diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 8af60ccc5..ce687ed10 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -170,7 +170,7 @@ namespace cryptonote BEGIN_SERIALIZE() VARINT_FIELD(version) - if (version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; + if (version == 0 || (version != 123 && CURRENT_TRANSACTION_VERSION < version)) return false; VARINT_FIELD(unlock_time) FIELD(vin) FIELD(vout) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 97f34f214..5ae16ca22 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1158,64 +1158,64 @@ namespace cryptonote //--------------------------------------------------------------- bool add_graft_tx_extra_to_extra(transaction &tx, const supernode::GraftTxExtra &graft_extra) { - return add_graft_tx_extra_to_extra(tx.extra, graft_extra); + return add_graft_tx_extra_to_extra(tx.extra, graft_extra); } //--------------------------------------------------------------- bool add_graft_tx_extra_to_extra(std::vector& extra, const supernode::GraftTxExtra &graft_extra) { - std::string blob; - ::serialization::dump_binary(const_cast(graft_extra), blob); - tx_extra_graft_extra container; - container.data = blob; - blob.clear(); - ::serialization::dump_binary(container, blob); - extra.push_back(TX_EXTRA_GRAFT_EXTRA_TAG); - std::copy(blob.begin(), blob.end(), std::back_inserter(extra)); - return true; + std::string blob; + ::serialization::dump_binary(const_cast(graft_extra), blob); + tx_extra_graft_extra container; + container.data = blob; + blob.clear(); + ::serialization::dump_binary(container, blob); + extra.push_back(TX_EXTRA_GRAFT_EXTRA_TAG); + std::copy(blob.begin(), blob.end(), std::back_inserter(extra)); + return true; } //--------------------------------------------------------------- bool get_graft_tx_extra_from_extra(const transaction &tx, supernode::GraftTxExtra &graft_tx_extra) { - std::vector tx_extra_fields; - parse_tx_extra(tx.extra, tx_extra_fields); - tx_extra_graft_extra graft_extra; - if(!find_tx_extra_field_by_type(tx_extra_fields, graft_extra)) - return false; - return ::serialization::parse_binary(graft_extra.data, graft_tx_extra); + std::vector tx_extra_fields; + parse_tx_extra(tx.extra, tx_extra_fields); + tx_extra_graft_extra graft_extra; + if(!find_tx_extra_field_by_type(tx_extra_fields, graft_extra)) + return false; + return ::serialization::parse_binary(graft_extra.data, graft_tx_extra); } namespace { - struct GraftStakeTxExtra - { - std::string supernode_public_id; - cryptonote::account_public_address supernode_public_address; - crypto::signature supernode_signature; - - GraftStakeTxExtra() = default; - - GraftStakeTxExtra(const std::string &supernode_public_id, - const cryptonote::account_public_address& supernode_public_address, - const crypto::signature &supernode_signature) : - supernode_public_id(supernode_public_id), - supernode_public_address(supernode_public_address), - supernode_signature(supernode_signature) - {} - - bool operator ==(const GraftStakeTxExtra &rhs) const { - return supernode_public_id == rhs.supernode_public_id && - !memcmp(&supernode_public_address.m_view_public_key.data[0], &rhs.supernode_public_address.m_view_public_key.data[0], - sizeof(supernode_public_address.m_view_public_key.data)) && - !memcmp(&supernode_signature.c.data[0], &rhs.supernode_signature.c.data[0], sizeof(supernode_signature.c.data)) && - !memcmp(&supernode_signature.r.data[0], &rhs.supernode_signature.r.data[0], sizeof(supernode_signature.r.data)); - } + struct GraftStakeTxExtra + { + std::string supernode_public_id; + cryptonote::account_public_address supernode_public_address; + crypto::signature supernode_signature; + + GraftStakeTxExtra() = default; + + GraftStakeTxExtra(const std::string &supernode_public_id, + const cryptonote::account_public_address& supernode_public_address, + const crypto::signature &supernode_signature) : + supernode_public_id(supernode_public_id), + supernode_public_address(supernode_public_address), + supernode_signature(supernode_signature) + {} + + bool operator ==(const GraftStakeTxExtra &rhs) const { + return supernode_public_id == rhs.supernode_public_id && + !memcmp(&supernode_public_address.m_view_public_key.data[0], &rhs.supernode_public_address.m_view_public_key.data[0], + sizeof(supernode_public_address.m_view_public_key.data)) && + !memcmp(&supernode_signature.c.data[0], &rhs.supernode_signature.c.data[0], sizeof(supernode_signature.c.data)) && + !memcmp(&supernode_signature.r.data[0], &rhs.supernode_signature.r.data[0], sizeof(supernode_signature.r.data)); + } - BEGIN_SERIALIZE_OBJECT() - FIELD(supernode_public_id) - FIELD(supernode_public_address) - FIELD(supernode_signature) - END_SERIALIZE() - }; + BEGIN_SERIALIZE_OBJECT() + FIELD(supernode_public_id) + FIELD(supernode_public_address) + FIELD(supernode_signature) + END_SERIALIZE() + }; } bool add_graft_stake_tx_extra_to_extra @@ -1224,16 +1224,16 @@ namespace cryptonote const cryptonote::account_public_address &supernode_public_address, const crypto::signature &supernode_signature) { - GraftStakeTxExtra tx_extra(supernode_public_id, supernode_public_address, supernode_signature); - std::string blob; - ::serialization::dump_binary(tx_extra, blob); - tx_extra_graft_stake_tx container; - container.data = blob; - blob.clear(); - ::serialization::dump_binary(container, blob); - extra.push_back(TX_EXTRA_GRAFT_STAKE_TX_TAG); - std::copy(blob.begin(), blob.end(), std::back_inserter(extra)); - return true; + GraftStakeTxExtra tx_extra(supernode_public_id, supernode_public_address, supernode_signature); + std::string blob; + ::serialization::dump_binary(tx_extra, blob); + tx_extra_graft_stake_tx container; + container.data = blob; + blob.clear(); + ::serialization::dump_binary(container, blob); + extra.push_back(TX_EXTRA_GRAFT_STAKE_TX_TAG); + std::copy(blob.begin(), blob.end(), std::back_inserter(extra)); + return true; } bool add_graft_rta_header_to_extra(std::vector &extra, const rta_header &rta_header) @@ -1261,13 +1261,13 @@ namespace cryptonote bool add_graft_tx_secret_key_to_extra(std::vector &extra, const crypto::secret_key& secret_key) { - tx_extra_graft_tx_secret_key container; - container.secret_key = secret_key; - std::string blob; - ::serialization::dump_binary(container, blob); - extra.push_back(TX_EXTRA_GRAFT_TX_SECRET_KEY_TAG); - std::copy(blob.begin(), blob.end(), std::back_inserter(extra)); - return true; + tx_extra_graft_tx_secret_key container; + container.secret_key = secret_key; + std::string blob; + ::serialization::dump_binary(container, blob); + extra.push_back(TX_EXTRA_GRAFT_TX_SECRET_KEY_TAG); + std::copy(blob.begin(), blob.end(), std::back_inserter(extra)); + return true; } bool get_graft_stake_tx_extra_from_extra @@ -1277,30 +1277,30 @@ namespace cryptonote crypto::signature &supernode_signature, crypto::secret_key &tx_secret_key) { - std::vector tx_extra_fields; - parse_tx_extra(tx.extra, tx_extra_fields); + std::vector tx_extra_fields; + parse_tx_extra(tx.extra, tx_extra_fields); - tx_extra_graft_stake_tx stake_tx_extra; + tx_extra_graft_stake_tx stake_tx_extra; - if(!find_tx_extra_field_by_type(tx_extra_fields, stake_tx_extra)) - return false; + if(!find_tx_extra_field_by_type(tx_extra_fields, stake_tx_extra)) + return false; - GraftStakeTxExtra stake_tx; + GraftStakeTxExtra stake_tx; - if (!::serialization::parse_binary(stake_tx_extra.data, stake_tx)) - return false; + if (!::serialization::parse_binary(stake_tx_extra.data, stake_tx)) + return false; - tx_extra_graft_tx_secret_key stake_tx_secret_key_extra; + tx_extra_graft_tx_secret_key stake_tx_secret_key_extra; - if(!find_tx_extra_field_by_type(tx_extra_fields, stake_tx_secret_key_extra)) - return false; + if(!find_tx_extra_field_by_type(tx_extra_fields, stake_tx_secret_key_extra)) + return false; - supernode_public_id = stake_tx.supernode_public_id; - supernode_public_address = stake_tx.supernode_public_address; - supernode_signature = stake_tx.supernode_signature; - tx_secret_key = stake_tx_secret_key_extra.secret_key; + supernode_public_id = stake_tx.supernode_public_id; + supernode_public_address = stake_tx.supernode_public_address; + supernode_signature = stake_tx.supernode_signature; + tx_secret_key = stake_tx_secret_key_extra.secret_key; - return true; + return true; } bool add_graft_rta_signatures_to_extra2(std::vector &extra, const std::vector &rta_signatures) @@ -1325,4 +1325,79 @@ namespace cryptonote return false; return ::serialization::parse_binary(rta_signatures_data.data, rta_signatures); } + + bool graft_get_disqualification(const transaction &tx, tx_extra_graft_disqualification& disq) + { + if(tx.version != 123) + return false; + if(!tx.vin.empty() || !tx.vout.empty() || tx.rct_signatures.txnFee !=0) + return false; + std::vector tx_extra_fields; + parse_tx_extra(tx.extra, tx_extra_fields); + return find_tx_extra_field_by_type(tx_extra_fields, disq); + } + + bool graft_is_disqualification(const transaction &tx) + { + tx_extra_graft_disqualification disq; + return graft_get_disqualification(tx, disq); + } + + bool graft_check_disqualification(const transaction &tx, tx_extra_graft_disqualification* pdisq) + { + tx_extra_graft_disqualification disq; + if(!graft_get_disqualification(tx, disq)) + return false; + {//check signs + std::string item_str; + ::serialization::dump_binary(disq.item, item_str); + crypto::hash hash; + crypto::cn_fast_hash(item_str.data(), item_str.size(), hash); + for(auto& si : disq.signers) + { + if(!crypto::check_signature(hash, si.signer_id, si.sign)) + return false; + } + } + if(pdisq) *pdisq = std::move(disq); + return true; + } + + bool graft_get_disqualification2(const transaction &tx, tx_extra_graft_disqualification2& disq) + { + if(tx.version != 124) + return false; + if(!tx.vin.empty() || !tx.vout.empty() || tx.rct_signatures.txnFee !=0) + return false; + std::vector tx_extra_fields; + parse_tx_extra(tx.extra, tx_extra_fields); + return find_tx_extra_field_by_type(tx_extra_fields, disq); + } + + bool graft_is_disqualification2(const transaction &tx) + { + tx_extra_graft_disqualification2 disq; + return graft_get_disqualification2(tx, disq); + } + + bool graft_check_disqualification2(const transaction &tx, tx_extra_graft_disqualification2* pdisq) + { + tx_extra_graft_disqualification2 disq; + if(!graft_get_disqualification2(tx, disq)) + return false; + {//check signs + std::string item_str; + ::serialization::dump_binary(disq.item, item_str); + crypto::hash hash; + crypto::cn_fast_hash(item_str.data(), item_str.size(), hash); + for(auto& si : disq.signers) + { + if(!crypto::check_signature(hash, si.signer_id, si.sign)) + return false; + } + } + if(pdisq) *pdisq = std::move(disq); + return true; + } + } diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 67dcb208c..fbc4f3cd4 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -166,6 +166,14 @@ namespace cryptonote */ bool get_graft_rta_signatures_from_extra2(const transaction& tx, std::vector &rta_signatures); + bool graft_get_disqualification(const transaction &tx, tx_extra_graft_disqualification& disq); + bool graft_is_disqualification(const transaction &tx); + bool graft_check_disqualification(const transaction &tx, tx_extra_graft_disqualification* pdisq = nullptr); + + bool graft_get_disqualification2(const transaction &tx, tx_extra_graft_disqualification2& disq); + bool graft_is_disqualification2(const transaction &tx); + bool graft_check_disqualification2(const transaction &tx, tx_extra_graft_disqualification2* pdisq = nullptr); + bool add_extra_nonce_to_tx_extra(std::vector& tx_extra, const blobdata& extra_nonce); bool remove_field_from_tx_extra(std::vector& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 38e6af4ef..0871563a1 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -46,6 +46,8 @@ #define TX_EXTRA_GRAFT_TX_SECRET_KEY_TAG 0x81 #define TX_EXTRA_GRAFT_RTA_HEADER_TAG 0x83 #define TX_EXTRA_GRAFT_RTA_SIGNATURES_TAG 0x84 +#define TX_EXTRA_GRAFT_DISQUALIFICATION_TAG 0x85 +#define TX_EXTRA_GRAFT_DISQUALIFICATION2_TAG 0x86 #define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE #define TX_EXTRA_NONCE_PAYMENT_ID 0x00 @@ -229,6 +231,83 @@ namespace cryptonote END_SERIALIZE() }; + struct tx_extra_graft_signer_item + { + uint64_t block_height; + crypto::hash block_hash; + crypto::public_key id; + BEGIN_SERIALIZE() + FIELD(block_height) + FIELD(block_hash) + FIELD(id) + END_SERIALIZE() + }; + + struct tx_extra_graft_disqualification + { + struct disqualification_item + { + uint64_t block_height; + crypto::hash block_hash; + crypto::public_key id; + BEGIN_SERIALIZE() + FIELD(block_height) + FIELD(block_hash) + FIELD(id) + END_SERIALIZE() + }; + + struct signer_item + { + crypto::public_key signer_id; + crypto::signature sign; + BEGIN_SERIALIZE() + FIELD(signer_id) + FIELD(sign) + END_SERIALIZE() + }; + + disqualification_item item; + std::vector signers; + BEGIN_SERIALIZE() + FIELD(item) + FIELD(signers) + END_SERIALIZE() + }; + + struct tx_extra_graft_disqualification2 + { + struct disqualification_item + { + std::string payment_id; + uint64_t block_height; + crypto::hash block_hash; + std::vector ids; + BEGIN_SERIALIZE() + FIELD(payment_id) + FIELD(block_height) + FIELD(block_hash) + FIELD(ids) + END_SERIALIZE() + }; + + struct signer_item + { + crypto::public_key signer_id; + crypto::signature sign; + BEGIN_SERIALIZE() + FIELD(signer_id) + FIELD(sign) + END_SERIALIZE() + }; + + disqualification_item item; + std::vector signers; + BEGIN_SERIALIZE() + FIELD(item) + FIELD(signers) + END_SERIALIZE() + }; // tx_extra_field format, except tx_extra_padding and tx_extra_pub_key: // varint tag; @@ -236,7 +315,8 @@ namespace cryptonote // varint data[]; typedef boost::variant tx_extra_field; + tx_extra_graft_tx_secret_key, tx_extra_graft_rta_header, tx_extra_graft_rta_signatures, + tx_extra_graft_disqualification, tx_extra_graft_disqualification2> tx_extra_field; } VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING); @@ -250,3 +330,5 @@ VARIANT_TAG(binary_archive, cryptonote::tx_extra_graft_stake_tx, TX_EXTRA_GRAFT_ VARIANT_TAG(binary_archive, cryptonote::tx_extra_graft_tx_secret_key, TX_EXTRA_GRAFT_TX_SECRET_KEY_TAG); VARIANT_TAG(binary_archive, cryptonote::tx_extra_graft_rta_header, TX_EXTRA_GRAFT_RTA_HEADER_TAG); VARIANT_TAG(binary_archive, cryptonote::tx_extra_graft_rta_signatures, TX_EXTRA_GRAFT_RTA_SIGNATURES_TAG); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_graft_disqualification, TX_EXTRA_GRAFT_DISQUALIFICATION_TAG); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_graft_disqualification2, TX_EXTRA_GRAFT_DISQUALIFICATION2_TAG); diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index c9ca49598..8770bb17a 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -33,9 +33,13 @@ set(cryptonote_core_sources cryptonote_tx_utils.cpp stake_transaction_storage.cpp stake_transaction_processor.cpp - blockchain_based_list.cpp) + blockchain_based_list.cpp + ../utils/sample_generator.cpp +) -set(cryptonote_core_headers) +set(cryptonote_core_headers + ../utils/sample_generator.h +) set(cryptonote_core_private_headers blockchain_storage_boost_serialization.h diff --git a/src/cryptonote_core/blockchain_based_list.cpp b/src/cryptonote_core/blockchain_based_list.cpp index ec178d8ac..b1cc32e4c 100644 --- a/src/cryptonote_core/blockchain_based_list.cpp +++ b/src/cryptonote_core/blockchain_based_list.cpp @@ -3,6 +3,7 @@ #include "graft_rta_config.h" #include "blockchain_based_list.h" #include "serialization/binary_utils.h" +#include "utils/sample_generator.h" using namespace cryptonote; @@ -42,22 +43,7 @@ const BlockchainBasedList::supernode_tier_array& BlockchainBasedList::tiers(size void BlockchainBasedList::select_supernodes(size_t items_count, const supernode_array& src_list, supernode_array& dst_list) { - size_t src_list_size = src_list.size(); - - if (items_count > src_list_size) - items_count = src_list_size; - - for (size_t i=0; i= items_count) - continue; - - dst_list.push_back(src_list[i]); - - items_count--; - } + graft::generator::uniform_select(graft::generator::do_not_seed{}, items_count, src_list, dst_list); } void BlockchainBasedList::apply_block(uint64_t block_height, const crypto::hash& block_hash, StakeTransactionStorage& stake_txs_storage) @@ -72,13 +58,11 @@ void BlockchainBasedList::apply_block(uint64_t block_height, const crypto::hash& //build blockchain based list for each tier - supernode_array prev_supernodes, current_supernodes; supernode_tier_array new_tier; for (size_t i=0; itier != i + 1) continue; + if (stake_txs_storage.is_disqualified(block_height, sn.supernode_public_id)) + continue; + prev_supernodes.push_back(sn); } } + supernode_array current_supernodes; current_supernodes.reserve(stakes.size()); for (const supernode_stake& stake : stakes) @@ -123,26 +111,19 @@ void BlockchainBasedList::apply_block(uint64_t block_height, const crypto::hash& current_supernodes.emplace_back(std::move(sn)); } - //seed RNG - - std::seed_seq seed(reinterpret_cast(&block_hash.data[0]), - reinterpret_cast(&block_hash.data[sizeof block_hash.data])); - - m_rng.seed(seed); - //sort valid supernodes by the age of stake std::stable_sort(current_supernodes.begin(), current_supernodes.end(), [](const supernode& s1, const supernode& s2) { return s1.block_height < s2.block_height || (s1.block_height == s2.block_height && s1.supernode_public_id < s2.supernode_public_id); }); + //seed RNG + graft::generator::seed_uniform_select(block_hash); //select supernodes from the previous list - supernode_array new_supernodes; - select_supernodes(PREVIOS_BLOCKCHAIN_BASED_LIST_MAX_SIZE, prev_supernodes, new_supernodes); - if (new_supernodes.size() < BLOCKCHAIN_BASED_LIST_SIZE) + if (new_supernodes.size() < BLOCKCHAIN_BASED_LIST_SIZE) //looks like it is always true { //remove supernodes of prev list from current list diff --git a/src/cryptonote_core/blockchain_based_list.h b/src/cryptonote_core/blockchain_based_list.h index 87e7a3b81..d1b6859c7 100644 --- a/src/cryptonote_core/blockchain_based_list.h +++ b/src/cryptonote_core/blockchain_based_list.h @@ -39,13 +39,13 @@ class BlockchainBasedList /// Constructors BlockchainBasedList(const std::string& file_name, uint64_t first_block_number); - /// List of tiers + /// List of tiers; resulting height is block_height - depth const supernode_tier_array& tiers(size_t depth = 0) const; /// Height of the corresponding block uint64_t block_height() const { return m_block_height; } - /// Number of blocks in history + /// Number of blocks in history; valid depth < history_depth() uint64_t history_depth() const { return m_history_depth; } /// Apply new block on top of the list @@ -72,7 +72,6 @@ class BlockchainBasedList list_history m_history; uint64_t m_block_height; size_t m_history_depth; - std::mt19937_64 m_rng; uint64_t m_first_block_number; mutable bool m_need_store; }; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 516d18cc1..bf00fd6eb 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -716,7 +716,7 @@ namespace cryptonote uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); // don't allow rta tx until hf 13 const size_t max_tx_version = version == 1 ? 1 : version < 13 ? 2 : CURRENT_TRANSACTION_VERSION; - if (tx.version == 0 || tx.version > max_tx_version) + if (tx.version == 0 || (tx.version != 123 && tx.version > max_tx_version)) { // v3 is the latest one we know tvc.m_verifivation_failed = true; @@ -971,6 +971,22 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const { + if(tx.version == 123) + { + if(tx.vin.size() || tx.vout.size()) + { + MERROR_VER("qualification tx with non-empty inputs or outputs, rejected for tx id= " << get_transaction_hash(tx)); + } + return graft_is_disqualification(tx); + } + if(tx.version == 124) + { + if(tx.vin.size() || tx.vout.size()) + { + MERROR_VER("qualification2 tx with non-empty inputs or outputs, rejected for tx id= " << get_transaction_hash(tx)); + } + return graft_is_disqualification2(tx); + } if(!tx.vin.size()) { MERROR_VER("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); diff --git a/src/cryptonote_core/stake_transaction_processor.cpp b/src/cryptonote_core/stake_transaction_processor.cpp index b774ca5f4..6e94f8bab 100644 --- a/src/cryptonote_core/stake_transaction_processor.cpp +++ b/src/cryptonote_core/stake_transaction_processor.cpp @@ -2,6 +2,8 @@ #include "stake_transaction_processor.h" #include "../graft_rta_config.h" +#include "serialization/binary_utils.h" +#include "../utils/sample_generator.h" #include @@ -148,6 +150,250 @@ void StakeTransactionProcessor::init_storages_impl() m_blockchain_based_list.reset(new BlockchainBasedList(m_config_dir + "/" + BLOCKCHAIN_BASED_LIST_FILE_NAME, first_block_number)); } +namespace +{ + +using TI = std::pair; //tier, index in the tier +constexpr int32_t TIERS = graft::generator::TIERS; +using Tiers = BlockchainBasedList::supernode_tier_array; +using Ids = std::vector; + +std::array, TIERS> makeBBLindexes(const Tiers& bbl_tiers) +{ + std::array, TIERS> res; + for(size_t t=0; tTI{ return std::make_pair(t, idx++); } ); + } + return res; +} + +Ids fromIndexes(const Tiers& bbl_tiers, const std::vector& idxs) +{ + Ids res; + res.reserve(idxs.size()); + for(auto& ti : idxs) + { + auto& t = ti.first; + auto& i = ti.second; + auto& supernode_public_id = bbl_tiers[t][i].supernode_public_id; + crypto::public_key id; + bool ok = epee::string_tools::hex_to_pod(supernode_public_id, id); + assert(ok); + res.emplace_back(std::move(id)); + } + return res; +} + +} //namespace + +bool StakeTransactionProcessor::check_disqualification_transaction(const transaction& tx, const crypto::hash tx_hash) +{ + assert(tx.version == 123); + + //TODO: find out if such disqualification transaction with tx_hash already exists + + tx_extra_graft_disqualification disq_extra; + if(!graft_check_disqualification(tx, &disq_extra)) + { + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash); + return false; + } + //TODO: it should be checked somewhere instead of this + /* + if(block_index <= disq_extra.item.block_height) + { + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash + << "; invalid block_height " << disq_extra.item.block_height << " current " << block_index); + return false; + } + */ + crypto::hash b_hash = m_blockchain.get_block_id_by_height(disq_extra.item.block_height); + if(b_hash != disq_extra.item.block_hash) + { + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash + << "; invalid block_hash "); + return false; + } + + //get BBL + size_t depth = m_blockchain_based_list->block_height() - disq_extra.item.block_height; + if(m_blockchain_based_list->history_depth() <= depth) + { + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash + << "; out of history "); + return false; + } + + if(disq_extra.signers.size() < graft::generator::REQUIRED_BBQS_VOTES) + { + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash + << "; lack of signers "); + return false; + } + auto& tiers = m_blockchain_based_list->tiers(depth); + + auto bbl_idxs = makeBBLindexes(tiers); + + std::vector bbqs_idxs, qcl_idxs; + graft::generator::select_BBQS_QCL(disq_extra.item.block_hash, bbl_idxs, bbqs_idxs, qcl_idxs); + Ids bbqs = fromIndexes(tiers, bbqs_idxs); + Ids qcl = fromIndexes(tiers, qcl_idxs); + + if(std::none_of(qcl.begin(), qcl.end(), [&disq_extra](crypto::public_key& v)->bool { return v == disq_extra.item.id; } )) + { + std::string id_str = epee::string_tools::pod_to_hex(disq_extra.item.id); + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash + << "; disqualified id " << id_str << " is not in QCL"); + return false; + } + + for(auto& si : disq_extra.signers) + { + if(std::none_of(bbqs.begin(), bbqs.end(), [&si](crypto::public_key& v)->bool { return v == si.signer_id; } )) + { + std::string id_str = epee::string_tools::pod_to_hex(si.signer_id); + MWARNING("Ignore invalid disqualification transaction, tx_hash=" << tx_hash + << "; signer id " << id_str << " is not in BBQS"); + return false; + } + } + + return true; +} + +bool StakeTransactionProcessor::check_disqualification2_transaction(const transaction& tx, const crypto::hash tx_hash) +{ + assert(tx.version == 124); + + //TODO: find out if such disqualification transaction with tx_hash already exists + + tx_extra_graft_disqualification2 disq_extra; + if(!graft_check_disqualification2(tx, &disq_extra)) + { + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash); + return false; + } + //TODO: it should be checked somewhere instead of this +/* + if(block_index <= disq_extra.item.block_height) + { + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash + << "; invalid block_height " << disq_extra.item.block_height << " current " << block_index); + return false; + } +*/ + crypto::hash b_hash = m_blockchain.get_block_id_by_height(disq_extra.item.block_height); + if(b_hash != disq_extra.item.block_hash) + { + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash + << "; invalid block_hash "); + return false; + } + + //get BBL + size_t depth = m_blockchain_based_list->block_height() - disq_extra.item.block_height; + if(m_blockchain_based_list->history_depth() <= depth) + { + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash + << "; out of history "); + return false; + } + + if(disq_extra.signers.size() < graft::generator::REQUIRED_DISQUAL2_VOTES) + { + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash + << "; lack of signers "); + return false; + } + auto& tiers = m_blockchain_based_list->tiers(depth); + + auto bbl_idxs = makeBBLindexes(tiers); + + std::vector auths_idxs; + graft::generator::select_AuthSample(disq_extra.item.payment_id, bbl_idxs, auths_idxs); + Ids auths = fromIndexes(tiers, auths_idxs); + + for(const auto& id : disq_extra.item.ids) + { + if(std::none_of(auths.begin(), auths.end(), [&id](crypto::public_key& v)->bool { return v == id; } )) + { + std::string id_str = epee::string_tools::pod_to_hex(id); + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash + << "; disqualified id " << id_str << " is not in the auth sample"); + return false; + } + } + + for(const auto& si : disq_extra.signers) + { + if(std::none_of(auths.begin(), auths.end(), [&si](crypto::public_key& v)->bool { return v == si.signer_id; } )) + { + std::string id_str = epee::string_tools::pod_to_hex(si.signer_id); + MWARNING("Ignore invalid disqualification2 transaction, tx_hash=" << tx_hash + << "; signer id " << id_str << " is not in the auth sample"); + return false; + } + } + + return true; +} + +void StakeTransactionProcessor::process_disqualification_transaction(const transaction& tx, const crypto::hash tx_hash, uint64_t block_index, const crypto::hash& block_hash, StakeTransactionStorage::disqualification_array& disquals) +{ + assert(tx.version == 123); + + tx_extra_graft_disqualification disq_extra; + if(!graft_get_disqualification(tx, disq_extra)) + { + MWARNING("Ignore invalid disqualification transaction at block #" << block_index << ", tx_hash=" << tx_hash); + return; + } + + disqualification disq; + serialization::dump_binary(disq_extra, disq.blob); + disq.block_index = block_index; + disq.id = disq_extra.item.id; + disq.id_str = epee::string_tools::pod_to_hex(disq.id); + + MDEBUG("New disqualification transaction found at block #" << block_index << ", tx_hash=" << tx_hash << ", supernode_id '" << disq.id_str << "'"); + + disquals.push_back(std::move(disq)); +} + +void StakeTransactionProcessor::process_disqualification2_transaction(const transaction& tx, const crypto::hash tx_hash, uint64_t block_index, const crypto::hash& block_hash, StakeTransactionStorage::disqualification2_storage_array& disquals2) +{ + assert(tx.version == 124); + + tx_extra_graft_disqualification2 disq_extra; + if(!graft_get_disqualification2(tx, disq_extra)) + { + MWARNING("Ignore invalid disqualification2 transaction at block #" << block_index << ", tx_hash=" << tx_hash); + return; + } + + disqualification2_storage_item disq; + serialization::dump_binary(disq_extra, disq.blob); + disq.block_index = block_index; + + std::string ids_str = "("; + { + bool comma = false; + for(const auto& id : disq_extra.item.ids) + { + if(comma) ids_str += ", "; + ids_str += epee::string_tools::pod_to_hex(id); + } + ids_str += ")"; + } + MDEBUG("New disqualification transaction found at block #" << block_index << ", tx_hash=" << tx_hash << ", disqualified supernode ids " << ids_str); + + disquals2.push_back(std::move(disq)); +} + void StakeTransactionProcessor::process_block_stake_transaction(uint64_t block_index, const block& block, const crypto::hash& block_hash, bool update_storage) { if (block_index <= m_storage->get_last_processed_block_index()) @@ -176,12 +422,28 @@ void StakeTransactionProcessor::process_block_stake_transaction(uint64_t block_i MWARNING(" " << tx_hash); } + StakeTransactionStorage::disqualification_array disquals; + StakeTransactionStorage::disqualification2_storage_array disquals2; + for (const transaction& tx : txs) { const crypto::hash tx_hash = get_transaction_prefix_hash(tx); try { + if(tx.version == 123) + { + process_disqualification_transaction(tx, tx_hash, block_index, block_hash, disquals); + continue; + } + if(tx.version == 124) + { + process_disqualification2_transaction(tx, tx_hash, block_index, block_hash, disquals2); + continue; + } + + stake_transaction stake_tx; + if (!get_graft_stake_tx_extra_from_extra(tx, stake_tx.supernode_public_id, stake_tx.supernode_public_address, stake_tx.supernode_signature, stake_tx.tx_secret_key)) continue; @@ -251,6 +513,9 @@ void StakeTransactionProcessor::process_block_stake_transaction(uint64_t block_i } } + m_storage->add_disquals(disquals); + m_storage->add_disquals2(disquals2); + m_stakes_need_update = true; //TODO: cache for stakes //update supernode stakes @@ -418,7 +683,7 @@ void StakeTransactionProcessor::invoke_update_stakes_handler_impl(uint64_t block if (!m_storage) return; - m_on_stakes_update(block_index, m_storage->get_supernode_stakes(block_index)); + m_on_stakes_update(block_index, m_storage->get_supernode_stakes(block_index), m_storage->get_supernode_disqualiications(block_index)); m_stakes_need_update = false; } @@ -466,7 +731,12 @@ void StakeTransactionProcessor::invoke_update_blockchain_based_list_handler_impl uint64_t height = m_blockchain_based_list->block_height(); for (size_t i=0; itiers(i)); + { + uint64_t block_height = height - i; + crypto::hash block_hash = m_blockchain.get_block_id_by_height(block_height); + + m_on_blockchain_based_list_update(block_height, block_hash, m_blockchain_based_list->tiers(i)); + } m_blockchain_based_list_need_update = false; } diff --git a/src/cryptonote_core/stake_transaction_processor.h b/src/cryptonote_core/stake_transaction_processor.h index 5d8bff0a7..c45bb3b8a 100644 --- a/src/cryptonote_core/stake_transaction_processor.h +++ b/src/cryptonote_core/stake_transaction_processor.h @@ -14,6 +14,7 @@ class StakeTransactionProcessor { public: typedef StakeTransactionStorage::supernode_stake_array supernode_stake_array; + typedef StakeTransactionStorage::supernode_disqualification_array supernode_disqualification_array; StakeTransactionProcessor(Blockchain& blockchain); @@ -26,7 +27,7 @@ class StakeTransactionProcessor /// Synchronize with blockchain void synchronize(); - typedef std::function supernode_stakes_update_handler; + typedef std::function supernode_stakes_update_handler; /// Update handler for new stakes void set_on_update_stakes_handler(const supernode_stakes_update_handler&); @@ -35,7 +36,7 @@ class StakeTransactionProcessor void invoke_update_stakes_handler(bool force = true); typedef BlockchainBasedList::supernode_tier_array supernode_tier_array; - typedef std::function blockchain_based_list_update_handler; + typedef std::function blockchain_based_list_update_handler; /// Update handler for new blockchain based list void set_on_update_blockchain_based_list_handler(const blockchain_based_list_update_handler&); @@ -46,9 +47,12 @@ class StakeTransactionProcessor /// Turns on/off processing void set_enabled(bool arg); - bool is_enabled() const; + /// Check that transaction with tx_hash does not exist yet. Those to be disqualified, and signers are in corresponding sets. + bool check_disqualification_transaction(const transaction& tx, const crypto::hash tx_hash); + bool check_disqualification2_transaction(const transaction& tx, const crypto::hash tx_hash); + private: void init_storages_impl(); void process_block(uint64_t block_index, const block& block, const crypto::hash& block_hash, bool update_storage = true); @@ -57,6 +61,9 @@ class StakeTransactionProcessor void process_block_stake_transaction(uint64_t block_index, const block& block, const crypto::hash& block_hash, bool update_storage = true); void process_block_blockchain_based_list(uint64_t block_index, const block& block, const crypto::hash& block_hash, bool update_storage = true); + void process_disqualification_transaction(const transaction& tx, const crypto::hash tx_hash, uint64_t block_index, const crypto::hash& block_hash, StakeTransactionStorage::disqualification_array& disquals); + void process_disqualification2_transaction(const transaction& tx, const crypto::hash tx_hash, uint64_t block_index, const crypto::hash& block_hash, StakeTransactionStorage::disqualification2_storage_array& disquals2); + private: std::string m_config_dir; Blockchain& m_blockchain; diff --git a/src/cryptonote_core/stake_transaction_storage.cpp b/src/cryptonote_core/stake_transaction_storage.cpp index ac3b61e36..f7bc93730 100644 --- a/src/cryptonote_core/stake_transaction_storage.cpp +++ b/src/cryptonote_core/stake_transaction_storage.cpp @@ -22,13 +22,19 @@ struct stake_transaction_file_data size_t last_processed_block_hashes_count; StakeTransactionStorage::stake_transaction_array& stake_txs; StakeTransactionStorage::block_hash_list& block_hashes; + StakeTransactionStorage::disqualification_array& disqualifications; + StakeTransactionStorage::disqualification2_storage_array& disqualifications2_storage; + stake_transaction_file_data(uint64_t in_last_processed_block_index, StakeTransactionStorage::stake_transaction_array& in_stake_txs, - size_t in_last_processed_block_hashes_count, StakeTransactionStorage::block_hash_list& in_block_hashes) + size_t in_last_processed_block_hashes_count, StakeTransactionStorage::block_hash_list& in_block_hashes, + StakeTransactionStorage::disqualification_array& disqualifications, StakeTransactionStorage::disqualification2_storage_array& disqualifications2_storage) : last_processed_block_index(in_last_processed_block_index) , last_processed_block_hashes_count(in_last_processed_block_hashes_count) , stake_txs(in_stake_txs) , block_hashes(in_block_hashes) + , disqualifications(disqualifications) + , disqualifications2_storage(disqualifications2_storage) { } @@ -37,6 +43,8 @@ struct stake_transaction_file_data FIELD(last_processed_block_hashes_count) FIELD(block_hashes) FIELD(stake_txs) + FIELD(disqualifications) + FIELD(disqualifications2_storage) END_SERIALIZE() }; @@ -60,6 +68,67 @@ void StakeTransactionStorage::add_tx(const stake_transaction& tx) m_need_store = true; } +void StakeTransactionStorage::add_disquals(const disqualification_array& disqs) +{ + if(disqs.empty()) return; + //sort and merge + auto middle = m_disqualifications.insert(m_disqualifications.end(), disqs.begin(), disqs.end()); + std::sort(middle, m_disqualifications.end(), disqualification::less_id_str); + std::inplace_merge(m_disqualifications.begin(), middle, m_disqualifications.end(), disqualification::less_id_str); + + m_need_store = true; +} + +StakeTransactionStorage::disqualification2_array StakeTransactionStorage::disquals2_from_storage(const disqualification2_storage_array& disqs_store) +{ + disqualification2_array disqs; + disqs.reserve(disqs_store.size()); //at least + for(const auto& item_store : disqs_store) + { + tx_extra_graft_disqualification2 extra_d2; + bool res = serialization::parse_binary(item_store.blob, extra_d2); + assert(res); + for(const auto& id : extra_d2.item.ids) + { + disqualification2 d2; + d2.block_index = item_store.block_index; + d2.id_str = epee::string_tools::pod_to_hex(id); + disqs.emplace_back( std::move(d2) ); + } + } + //sort + std::sort(disqs.begin(), disqs.end(), disqualification2::less_id_str); + return disqs; +} + +void StakeTransactionStorage::add_disquals2(const disqualification2_storage_array& disqs_store) +{ + if(disqs_store.empty()) return; + m_disqualifications2_storage.insert(m_disqualifications2_storage.end(), disqs_store.begin(), disqs_store.end()); + //make disqualification2 array from storage items + disqualification2_array disqs = disquals2_from_storage(disqs_store); + //merge + auto middle = m_disqualifications2.insert(m_disqualifications2.end(), disqs.begin(), disqs.end()); + std::inplace_merge(m_disqualifications2.begin(), middle, m_disqualifications2.end(), disqualification2::less_id_str); + + m_need_store = true; +} + +bool StakeTransactionStorage::is_disqualified(uint64_t block_number, const std::string& supernode_public_id) const +{ + {//disqualification + disqualification tmp; tmp.id_str = supernode_public_id; + auto pair = std::equal_range(m_disqualifications.begin(), m_disqualifications.end(), tmp, disqualification::less_id_str); + bool res = std::any_of(pair.first, pair.second, [block_number](const disqualification& v){ return v.is_active_for(block_number); }); + if(res) return true; + } + {//disqualification2 + disqualification2 tmp; tmp.id_str = supernode_public_id; + auto pair = std::equal_range(m_disqualifications2.begin(), m_disqualifications2.end(), tmp, disqualification2::less_id_str); + return std::any_of(pair.first, pair.second, [block_number](const disqualification2& v){ return v.is_active_for(block_number); }); + } +} + const crypto::hash& StakeTransactionStorage::get_last_processed_block_hash() const { if (m_last_processed_block_hashes.empty()) @@ -100,6 +169,20 @@ void StakeTransactionStorage::remove_last_processed_block() return tx.block_height == m_last_processed_block_index; }), m_stake_txs.end()); + //rollback disqualifications + m_disqualifications.erase(std::remove_if(m_disqualifications.begin(), m_disqualifications.end(), [&](const disqualification& d) { + return d.block_index == m_last_processed_block_index; + }), m_disqualifications.end()); + + m_disqualifications2.erase(std::remove_if(m_disqualifications2.begin(), m_disqualifications2.end(), [&](const disqualification2& d) { + return d.block_index == m_last_processed_block_index; + }), m_disqualifications2.end()); + + m_disqualifications2_storage.erase(std::remove_if(m_disqualifications2_storage.begin(), m_disqualifications2_storage.end(), [&](const disqualification2_storage_item& d) { + return d.block_index == m_last_processed_block_index; + }), m_disqualifications2_storage.end()); + + // m_last_processed_block_hashes_count--; m_last_processed_block_index--; @@ -121,10 +204,17 @@ const StakeTransactionStorage::supernode_stake_array& StakeTransactionStorage::g return m_supernode_stakes; } +const StakeTransactionStorage::supernode_disqualification_array& StakeTransactionStorage::get_supernode_disqualiications(uint64_t block_number) +{ + update_supernode_stakes(block_number); + return m_supernode_disqualifications; +} + void StakeTransactionStorage::clear_supernode_stakes() { m_supernode_stakes.clear(); m_supernode_stake_indexes.clear(); + m_supernode_disqualifications.clear(); m_supernode_stakes_update_block_number = 0; } @@ -152,9 +242,19 @@ void StakeTransactionStorage::update_supernode_stakes(uint64_t block_number) m_supernode_stakes.clear(); m_supernode_stake_indexes.clear(); + m_supernode_disqualifications.clear(); try { + //disqualifications + for(const auto& disq : m_disqualifications) + { + if(!disq.is_active_for(block_number)) + continue; + m_supernode_disqualifications.push_back(disq.blob); + } + + //stakes m_supernode_stakes.reserve(m_stake_txs.size()); for (const stake_transaction& tx : m_stake_txs) @@ -270,6 +370,7 @@ void StakeTransactionStorage::update_supernode_stakes(uint64_t block_number) { m_supernode_stakes.clear(); m_supernode_stake_indexes.clear(); + m_supernode_disqualifications.clear(); m_supernode_stakes_update_block_number = 0; @@ -307,7 +408,9 @@ void StakeTransactionStorage::load() StakeTransactionStorage::stake_transaction_array tmp_stake_txs; StakeTransactionStorage::block_hash_list tmp_block_hashes; - stake_transaction_file_data data(0, tmp_stake_txs, 0, tmp_block_hashes); + StakeTransactionStorage::disqualification_array tmp_disqualifications; + StakeTransactionStorage::disqualification2_storage_array tmp_disqualifications2_storage; + stake_transaction_file_data data(0, tmp_stake_txs, 0, tmp_block_hashes, tmp_disqualifications, tmp_disqualifications2_storage); r = ::serialization::parse_binary(buffer, data); @@ -318,6 +421,11 @@ void StakeTransactionStorage::load() std::swap(m_stake_txs, data.stake_txs); std::swap(m_last_processed_block_hashes, data.block_hashes); + std::swap(m_disqualifications, data.disqualifications); + std::swap(m_disqualifications2_storage, data.disqualifications2_storage); + + disqualification2_array disqs2 = disquals2_from_storage(m_disqualifications2_storage); + m_disqualifications2.swap(disqs2); m_need_store = false; } @@ -331,7 +439,9 @@ void StakeTransactionStorage::load() void StakeTransactionStorage::store() const { stake_transaction_file_data data(m_last_processed_block_index, const_cast(m_stake_txs), - m_last_processed_block_hashes_count, const_cast(m_last_processed_block_hashes)); + m_last_processed_block_hashes_count, const_cast(m_last_processed_block_hashes) + , const_cast(m_disqualifications) + , const_cast(m_disqualifications2_storage)); std::ofstream ostr; ostr.open(m_storage_file_name, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); diff --git a/src/cryptonote_core/stake_transaction_storage.h b/src/cryptonote_core/stake_transaction_storage.h index f8d1d3b56..d9bde52f6 100644 --- a/src/cryptonote_core/stake_transaction_storage.h +++ b/src/cryptonote_core/stake_transaction_storage.h @@ -49,12 +49,78 @@ struct supernode_stake cryptonote::account_public_address supernode_public_address; }; +//TODO: find good place for it common with supernode +constexpr size_t DISQUALIFICATION_DURATION_BLOCK_COUNT = 10; +constexpr size_t DISQUALIFICATION2_DURATION_BLOCK_COUNT = 10; + +struct disqualification +{ + std::string blob; //tx_extra_graft_disqualification + crypto::public_key id; + uint64_t block_index; //block_index where transaction is stored, not that from the blob. disqualifications should be applied from next block + //uint64_t unlock_time == block_height + DISQUALIFICATION_DURATION_BLOCK_COUNT + std::string id_str; + + BEGIN_SERIALIZE_OBJECT() + FIELD(blob) + FIELD(id) + FIELD(block_index) + if (!typename Archive::is_saving()) + { + id_str = epee::string_tools::pod_to_hex(id); + } + END_SERIALIZE() + + bool is_active_for(uint64_t block_index_target) const + { + //from next block where the transaction is stored + return block_index < block_index_target && block_index_target <= block_index + DISQUALIFICATION_DURATION_BLOCK_COUNT; + } + + static bool less_id_str(const disqualification& l, const disqualification& r) + { + return l.id_str < r.id_str; + } +}; + +struct disqualification2_storage_item +{ + std::string blob; //tx_extra_graft_disqualification2 + uint64_t block_index; //block_index where transaction is stored, not that from the blob. disqualifications should be applied from next block + BEGIN_SERIALIZE_OBJECT() + FIELD(blob) + FIELD(block_index) + END_SERIALIZE() +}; + +struct disqualification2 +{ + std::string id_str; + uint64_t block_index; //block_index where transaction is stored, not that from the blob. disqualifications should be applied from next block + //uint64_t unlock_time == block_height + DISQUALIFICATION2_DURATION_BLOCK_COUNT + + bool is_active_for(uint64_t block_index_target) const + { + //from next block where the transaction is stored + return block_index < block_index_target && block_index_target <= block_index + DISQUALIFICATION2_DURATION_BLOCK_COUNT; + } + + static bool less_id_str(const disqualification2& l, const disqualification2& r) + { + return l.id_str < r.id_str; + } +}; + class StakeTransactionStorage { public: typedef std::vector stake_transaction_array; typedef std::list block_hash_list; typedef std::vector supernode_stake_array; + typedef std::vector disqualification_array; + typedef std::vector disqualification2_storage_array; + typedef std::vector disqualification2_array; + typedef std::vector supernode_disqualification_array; StakeTransactionStorage(const std::string& storage_file_name, uint64_t first_block_number); @@ -62,7 +128,7 @@ class StakeTransactionStorage size_t get_tx_count() const { return m_stake_txs.size(); } /// Get transactions - const stake_transaction_array& get_txs() const { return m_stake_txs; } +// const stake_transaction_array& get_txs() const { return m_stake_txs; } /// Index of last processed block uint64_t get_last_processed_block_index() const { return m_last_processed_block_index; } @@ -81,12 +147,16 @@ class StakeTransactionStorage /// Add transaction void add_tx(const stake_transaction&); + void add_disquals(const disqualification_array& disqs); + void add_disquals2(const disqualification2_storage_array& disqs_store); /// List of supernode stakes const supernode_stake_array& get_supernode_stakes(uint64_t block_number); + const supernode_disqualification_array& get_supernode_disqualiications(uint64_t block_number); /// Search supernode stake by supernode public id (returns nullptr if no stake is found) const supernode_stake* find_supernode_stake(uint64_t block_number, const std::string& supernode_public_id); + bool is_disqualified(uint64_t block_number, const std::string& supernode_public_id) const; /// Update supernode stakes void update_supernode_stakes(uint64_t block_number); @@ -106,6 +176,9 @@ class StakeTransactionStorage typedef std::unordered_map supernode_stake_index_map; + //returns sorted array of disqualification2 from storage items + disqualification2_array disquals2_from_storage(const disqualification2_storage_array& disqs_store); + private: std::string m_storage_file_name; uint64_t m_last_processed_block_index; @@ -113,10 +186,15 @@ class StakeTransactionStorage size_t m_last_processed_block_hashes_count; stake_transaction_array m_stake_txs; uint64_t m_supernode_stakes_update_block_number; - supernode_stake_array m_supernode_stakes; - supernode_stake_index_map m_supernode_stake_indexes; + supernode_stake_array m_supernode_stakes; //an array valid for m_supernode_stakes_update_block_number + supernode_stake_index_map m_supernode_stake_indexes; //maps supernode_id to index in m_supernode_stakes, valid for m_supernode_stakes_update_block_number uint64_t m_first_block_number; mutable bool m_need_store; + + disqualification_array m_disqualifications; //sorted by id_str + disqualification2_array m_disqualifications2; //sorted by id_str + disqualification2_storage_array m_disqualifications2_storage; + supernode_disqualification_array m_supernode_disqualifications; //an array valid for m_supernode_stakes_update_block_number }; } diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index dff83dbd6..24c5517bd 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -188,8 +188,33 @@ namespace cryptonote // 1. if tx.type == tx_type_rta and tx.rta_signatures.size() > 0 // 2. if tx.version >= 3 and tx.rta_signatures.size() > 0 + bool is_disqualification_tx = (tx.version == 123); + bool is_disqualification2_tx = (tx.version == 124); bool is_rta_tx = tx.type == transaction::tx_type_rta; - if (is_rta_tx) { + if(is_disqualification_tx) + { + //kept_by_block means that we already have checked the transaction once, and the check should pass. + //But in case we pop more than one block, and as such pop others stake and disqualification transactions in the popped blocks, + //it is possible that check of disqualification transaction will not pass again because of the history change, + //Note, that history change means a change of BBL as a function of block_height and previous stakes and disqualifications. + //Currently the fact is ignored, if we had checked once I assume the disqualifications will be valid, even if cannot be checked against previous history. + if(!kept_by_block && !m_stp->check_disqualification_transaction(tx, id)) + { + MERROR("Invalid disqualification transaction with id " << id); + tvc.m_verifivation_failed = true; + return false; + } + } + else if (is_disqualification2_tx) + { + if(!kept_by_block && !m_stp->check_disqualification2_transaction(tx, id)) + { + MERROR("Invalid disqualification2 transaction with id " << id); + tvc.m_verifivation_failed = true; + return false; + } + } + else if (is_rta_tx) { cryptonote::rta_header rta_hdr; if (!cryptonote::get_graft_rta_header_from_extra(tx, rta_hdr)) { MERROR("Failed to parse rta-header from tx extra: " << id); @@ -262,7 +287,8 @@ namespace cryptonote crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; cryptonote::txpool_tx_meta_t meta; - bool ch_inp_res = check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, id, max_used_block_height, max_used_block_id, tvc, kept_by_block); + bool ch_inp_res = is_disqualification_tx || is_disqualification2_tx + || check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, id, max_used_block_height, max_used_block_id, tvc, kept_by_block); if(!ch_inp_res) { // if the transaction was valid before (kept_by_block), then it diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index bd035f622..1067fb301 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -465,8 +465,8 @@ namespace nodetool uint64_t get_multicast_bytes_out() const { return m_multicast_bytes_out; } private: - void handle_stakes_update(uint64_t block_number, const cryptonote::StakeTransactionProcessor::supernode_stake_array& stakes); - void handle_blockchain_based_list_update(uint64_t block_number, const cryptonote::StakeTransactionProcessor::supernode_tier_array& tiers); + void handle_stakes_update(uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_stake_array& stakes, const cryptonote::StakeTransactionProcessor::supernode_disqualification_array& disquals); + void handle_blockchain_based_list_update(uint64_t block_number, const crypto::hash& block_hash, const cryptonote::StakeTransactionProcessor::supernode_tier_array& tiers); private: std::multimap m_supernode_requests_timestamps; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 2c49e4ee1..b475da052 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -547,11 +547,11 @@ namespace nodetool bool node_server::init(const boost::program_options::variables_map& vm) { m_payload_handler.get_core().set_update_stakes_handler( - [&](uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_stake_array& stakes) { handle_stakes_update(block_height, stakes); } + [&](uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_stake_array& stakes, const cryptonote::StakeTransactionProcessor::supernode_disqualification_array& disquals) { handle_stakes_update(block_height, stakes, disquals); } ); m_payload_handler.get_core().set_update_blockchain_based_list_handler( - [&](uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_tier_array& tiers) { handle_blockchain_based_list_update(block_height, tiers); } + [&](uint64_t block_height, const crypto::hash& block_hash, const cryptonote::StakeTransactionProcessor::supernode_tier_array& tiers) { handle_blockchain_based_list_update(block_height, block_hash, tiers); } ); std::set full_addrs; @@ -3162,7 +3162,7 @@ namespace nodetool } template - void node_server::handle_stakes_update(uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_stake_array& stakes) + void node_server::handle_stakes_update(uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_stake_array& stakes, const cryptonote::StakeTransactionProcessor::supernode_disqualification_array& disquals) { static std::string supernode_endpoint("send_supernode_stakes"); @@ -3193,6 +3193,8 @@ namespace nodetool request.stakes.emplace_back(std::move(dst_stake)); } + request.disqualifications = disquals; + post_request_to_supernodes(supernode_endpoint, request); } @@ -3203,7 +3205,7 @@ namespace nodetool } template - void node_server::handle_blockchain_based_list_update(uint64_t block_height, const cryptonote::StakeTransactionProcessor::supernode_tier_array& tiers) + void node_server::handle_blockchain_based_list_update(uint64_t block_height, const crypto::hash& block_hash, const cryptonote::StakeTransactionProcessor::supernode_tier_array& tiers) { static std::string supernode_endpoint("blockchain_based_list"); @@ -3217,6 +3219,7 @@ namespace nodetool cryptonote::COMMAND_RPC_SUPERNODE_BLOCKCHAIN_BASED_LIST::request request; request.block_height = block_height; + request.block_hash = epee::string_tools::pod_to_hex(block_hash); for (size_t i=0; i stakes; + std::vector disqualifications; //tx_extra_graft_disqualification; is it required? looks like is not BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_height) KV_SERIALIZE(stakes) + KV_SERIALIZE(disqualifications) END_KV_SERIALIZE_MAP() }; @@ -2338,9 +2340,11 @@ namespace cryptonote struct request { uint64_t block_height; + std::string block_hash; std::vector tiers; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_height) + KV_SERIALIZE(block_hash) KV_SERIALIZE(tiers) END_KV_SERIALIZE_MAP() }; diff --git a/src/utils/sample_generator.cpp b/src/utils/sample_generator.cpp new file mode 100644 index 000000000..bc6a66c58 --- /dev/null +++ b/src/utils/sample_generator.cpp @@ -0,0 +1,60 @@ +// Copyright (c) 2019, The Graft Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#include "sample_generator.h" + +namespace graft { namespace generator { + +namespace detail +{ + +std::mt19937_64& get_rnd() +{ + thread_local static std::mt19937_64 rnd; + return rnd; +} + +} //namespace detail + +void seed_uniform_select(std::seed_seq& sseq) +{ + auto& rnd = detail::get_rnd(); + rnd.seed(sseq); +} + +void seed_uniform_select(const std::string& str) +{ + assert(!str.empty() && str.size()%sizeof(uint32_t) == 0); + size_t count = str.size()/sizeof(uint32_t); + const uint32_t* p = reinterpret_cast(str.data()); + std::seed_seq sseq(p, p+count); + seed_uniform_select(sseq); +} + +}} //namespace graft::crypto_tools diff --git a/src/utils/sample_generator.h b/src/utils/sample_generator.h new file mode 100644 index 000000000..6765097cd --- /dev/null +++ b/src/utils/sample_generator.h @@ -0,0 +1,222 @@ +// Copyright (c) 2019, The Graft Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#pragma once + +#include +#include +#include +#include + +#include "crypto/crypto.h" +#include "misc_log_ex.h" + +namespace graft { namespace generator { + +namespace detail +{ + +std::mt19937_64& get_rnd(); + +} //namespace detail + +template +void uniform_select(std::mt19937_64& rnd, size_t count, const std::vector& src, std::vector& dst) +{ + size_t src_size = src.size(); + if (count > src_size) count = src_size; + for (size_t i=0; i= count) + continue; + dst.push_back(src[i]); + --count; + } +} + +struct do_not_seed{}; + +void seed_uniform_select(std::seed_seq& sseq); + +template +typename std::enable_if< !std::is_same< typename std::decay::type, std::seed_seq>::value >::type +seed_uniform_select(const POD& pod) +{ + static_assert( !std::is_same::type, do_not_seed>::value ); + static_assert( !std::is_same::type, std::string>::value ); + static_assert(std::is_trivially_copyable::value); + static_assert(sizeof(POD) % sizeof(uint32_t) == 0); + + auto* pb = reinterpret_cast(&pod); + auto* pe = pb + sizeof(POD) / sizeof(uint32_t); + std::seed_seq sseq(pb, pe); + seed_uniform_select(sseq); +} + +void seed_uniform_select(const std::string& str); + +template +typename std::enable_if< std::is_same::type, do_not_seed>::value>::type +uniform_select(DNS&& tmp, size_t count, const std::vector& src, std::vector& dst) +{ + auto& rnd = detail::get_rnd(); + uniform_select(rnd, count, src, dst); +} + +template +void uniform_select(std::seed_seq& seed, size_t count, const std::vector& src, std::vector& dst) +{ + auto& rnd = detail::get_rnd(); + rnd.seed(seed); + uniform_select(rnd, count, src, dst); +} + +template +typename std::enable_if< !std::is_same::type, do_not_seed>::value >::type +uniform_select(const POD& seed, size_t count, const std::vector& src, std::vector& dst) +{ + seed_uniform_select(seed); + uniform_select(do_not_seed{}, count, src, dst); +} + +constexpr int32_t TIERS = 4; +constexpr int32_t ITEMS_PER_TIER = 2; +constexpr int32_t AUTH_SAMPLE_SIZE = TIERS * ITEMS_PER_TIER; +constexpr int32_t BBQS_SIZE = TIERS * ITEMS_PER_TIER; +constexpr int32_t QCL_SIZE = TIERS * ITEMS_PER_TIER; +constexpr int32_t REQUIRED_BBQS_VOTES = (BBQS_SIZE*2 + (3-1))/3; +constexpr int32_t REQUIRED_DISQUAL2_VOTES = 5; + +/*! + * \brief selectSample - selects a sample such as BBQS and QCl. + * + * \param sample_size - required resulting size. + * \param bbl_tiers - tiers of somehow sorted items. + * \param out - resulting flat list. + * \param prefix - it is for logging. + * \return - false if resulting size less than requested sample_size + */ +template, TIERS>> +bool selectSample(size_t sample_size, const Tiers& bbl_tiers, std::vector& out, const char* prefix) +{ + assert(sample_size % TIERS == 0); + + //select sample_size for each tier + std::array, TIERS> tier_supernodes; + for (size_t i=0; i select; + select.fill(items_per_tier); + // If we are short of the needed SNs on any tier try selecting additional SNs from the highest + // tier with surplus SNs. For example, if tier 2 is short by 1, look for a surplus first at + // tier 4, then tier 3, then tier 1. + for (int i = 0; i < TIERS; i++) { + int deficit_i = select[i] - int(tier_supernodes[i].size()); + for (int j = TIERS-1; deficit_i > 0 && j >= 0; j--) { + if (i == j) continue; + int surplus_j = int(tier_supernodes[j].size()) - select[j]; + if (surplus_j > 0) { + // Tier j has more SNs than needed, so select an extra SN from tier j to make up for + // the deficiency at tier i. + int transfer = std::min(deficit_i, surplus_j); + select[i] -= transfer; + select[j] += transfer; + deficit_i -= transfer; + } + } + // If we still have a deficit then no other tier has a surplus; we'll just have to work with + // a smaller sample because there aren't enough SNs on the entire network. + if (deficit_i > 0) + select[i] -= deficit_i; + } + + out.clear(); + out.reserve(sample_size); + auto out_it = back_inserter(out); + for (int i = 0; i < TIERS; i++) { + std::copy(tier_supernodes[i].begin(), tier_supernodes[i].begin() + select[i], out_it); + } + + if (out.size() > sample_size) + out.resize(sample_size); + + MDEBUG("..." << out.size() << " supernodes has been selected"); + + return out.size() == sample_size; +} + +/*! + * \brief select_BBQS_QCL - generates BBQS and QCl from bbl. + * + * \param block_hash - hash of the block corresponding to BBL. + * \param bbl_tiers - tiers of somehow sorted items. + * \param bbqs - resulting BBQS. + * \param qcl - resulting QCL. + * \return - false if at least one of the resulting sizes less than desired sizes of BBQS or QCL + */ + +template, TIERS>> +bool select_BBQS_QCL(crypto::hash block_hash, const Tiers& bbl_tiers, std::vector& bbqs, std::vector& qcl) +{ + //seed once + generator::seed_uniform_select(block_hash); + bool res1 = selectSample(BBQS_SIZE, bbl_tiers, bbqs, "BBQS"); + bool res2 = selectSample(QCL_SIZE, bbl_tiers, qcl, "QCL"); + return res1 && res2; +} + +/*! + * \brief select_AuthSample - generates auth sample from bbl based on payment_id. + * + * \param payment_id + * \param bbl_tiers - tiers of somehow sorted items. + * \param auths - resulting auth sample. + * \return - false if resulting size less than desired auth sample size + */ + +template, TIERS>> +bool select_AuthSample(const std::string& payment_id, const Tiers& bbl_tiers, std::vector& auths) +{ + //seed once + generator::seed_uniform_select(payment_id); + bool res = selectSample(AUTH_SAMPLE_SIZE, bbl_tiers, auths, "auth"); + return res; +} + +}} //namespace graft::generator