diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 8af60ccc5..42a4481a4 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 && version != 124 && CURRENT_TRANSACTION_VERSION < version)) return false; VARINT_FIELD(unlock_time) FIELD(vin) FIELD(vout) diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 884250261..f5a22c4ab 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -93,9 +93,9 @@ namespace cryptonote { const int emission_speed_factor = EMISSION_SPEED_FACTOR_PER_MINUTE - (target_minutes-1); const uint64_t first_reward = 8301030000000000000U; - - if (version >= 6 && median_weight > 0 && already_generated_coins < first_reward) { + if ((version >= 6 || already_generated_coins > 0) && median_weight > 0 && already_generated_coins < first_reward) { reward = first_reward; + MDEBUG("premine triggered"); return true; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 97f34f214..2ab171f8c 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -381,7 +381,7 @@ namespace cryptonote //--------------------------------------------------------------- bool get_tx_fee(const transaction& tx, uint64_t & fee) { - if (tx.version > 1) + if (tx.version > 1 && tx.version != 123 && tx.version != 124) { fee = tx.rct_signatures.txnFee; return true; @@ -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,81 @@ 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; + //get_tx_fee(tx) should return 0 when tx.vin tx.vout are empty + if(!tx.vin.empty() || !tx.vout.empty()) + 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; + //get_tx_fee(tx) should return 0 when tx.vin tx.vout are empty + if(!tx.vin.empty() || !tx.vout.empty()) + 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.cpp b/src/cryptonote_core/blockchain.cpp index ec2ef57f8..49e1f2fdf 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3518,13 +3518,15 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& t_dblspnd += dd; TIME_MEASURE_START(cc); + bool is_disqual = (tx.version == 123 || tx.version == 124); + #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)) + if(!is_disqual && !check_tx_inputs(tx, tvc)) { MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); diff --git a/src/cryptonote_core/blockchain_based_list.cpp b/src/cryptonote_core/blockchain_based_list.cpp index ec178d8ac..3d3a42ff6 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-1, 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) @@ -112,6 +100,9 @@ void BlockchainBasedList::apply_block(uint64_t block_height, const crypto::hash& if (stake.tier != i + 1) continue; + if (stake_txs_storage.is_disqualified(block_height, stake.supernode_public_id)) + continue; + supernode sn; sn.supernode_public_id = stake.supernode_public_id; @@ -123,26 +114,20 @@ 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) { + //with such return stable_sort is not required + std::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..368f24967 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -56,6 +56,7 @@ using namespace epee; #include "ringct/rctSigs.h" #include "common/notify.h" #include "version.h" +#include "graft_rta_config.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "cn" @@ -342,9 +343,21 @@ namespace cryptonote if (get_arg(vm, arg_disable_stake_tx_processing)) { MWARNING("stake transaction processing disabled"); - m_graft_stake_transaction_processor.set_enabled(false); + m_graft_stake_transaction_processor.set_active_from_height(CRYPTONOTE_MAX_BLOCK_NUMBER); + } else { + const auto & hardforks = m_blockchain_storage.get_hard_fork_heights(m_nettype); + uint64_t stakes_active_height = 1; + for (const auto & hf: hardforks) { + if (hf.version == config::graft::STAKE_TRANSACTION_PROCESSING_DB_VERSION) { + stakes_active_height = hf.height; + break; + } + } + if (stakes_active_height == 1) { + MWARNING("Stake transaction processor: activation height not defined for nettype: " << m_nettype); + } + m_graft_stake_transaction_processor.set_active_from_height(stakes_active_height); } - return true; } //----------------------------------------------------------------------------------------------- @@ -716,7 +729,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 != 124 && tx.version > max_tx_version)) { // v3 is the latest one we know tvc.m_verifivation_failed = true; @@ -781,7 +794,7 @@ namespace cryptonote continue; } - if (tx_info[n].tx->version < 2) + if (tx_info[n].tx->version < 2 || tx_info[n].tx->version == 123 || tx_info[n].tx->version == 124) continue; const rct::rctSig &rv = tx_info[n].tx->rct_signatures; switch (rv.type) { @@ -971,6 +984,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)); @@ -1354,6 +1383,11 @@ namespace cryptonote uint64_t depth = m_blockchain_storage.get_current_blockchain_height() - last_received_block_height; m_graft_stake_transaction_processor.invoke_update_blockchain_based_list_handler(true, depth); } + + StakeTransactionProcessor *core::get_stake_tx_processor() + { + return &m_graft_stake_transaction_processor; + } //----------------------------------------------------------------------------------------------- bool core::prepare_handle_incoming_blocks(const std::vector &blocks) { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index b5996e0e6..d7a58fa4e 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -801,6 +801,12 @@ namespace cryptonote */ void invoke_update_blockchain_based_list_handler(uint64_t last_received_block_height); + /** + * @brief get_stake_tx_processor - returns stake tx processor + * @return pointer to StakeTransactionProcerssor + */ + StakeTransactionProcessor * get_stake_tx_processor(); + private: /** diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 72f804f18..249f59fa2 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -206,12 +206,13 @@ namespace cryptonote { hw::device &hwdev = sender_account_keys.get_device(); - if (sources.empty()) + if (sources.empty() && tx.version != 123 && tx.version != 124) { LOG_ERROR("Empty sources"); return false; } + size_t tx_version = (tx.version != 123 && tx.version != 124)? 0 : tx.version; std::vector amount_keys; tx.set_null(); amount_keys.clear(); @@ -220,7 +221,7 @@ namespace cryptonote msout->c.clear(); } - tx.version = rct ? (tx_type == transaction::tx_type_rta? 3 : 2) : 1; + tx.version = tx_version? tx_version : rct ? (tx_type == transaction::tx_type_rta? 3 : 2) : 1; tx.type = tx_type; tx.unlock_time = unlock_time; @@ -470,7 +471,7 @@ namespace cryptonote MDEBUG("Null secret key, skipping signatures"); } - if (tx.version == 1) + if (tx.version == 1 || tx.version == 123 || tx.version == 124) { //generate ring signatures crypto::hash tx_prefix_hash; diff --git a/src/cryptonote_core/stake_transaction_processor.cpp b/src/cryptonote_core/stake_transaction_processor.cpp index b774ca5f4..08a128ada 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 @@ -136,11 +138,7 @@ void StakeTransactionProcessor::init_storages_impl() if (m_storage || m_blockchain_based_list) throw std::runtime_error("StakeTransactionProcessor storages have been already initialized"); - uint64_t first_block_number = m_enabled ? m_blockchain.get_earliest_ideal_height_for_version(config::graft::STAKE_TRANSACTION_PROCESSING_DB_VERSION) - : std::numeric_limits().max(); - - if (first_block_number) - first_block_number--; + uint64_t first_block_number = m_active_height; MDEBUG("Initialize stake processing storages. First block height is " << first_block_number); @@ -148,17 +146,271 @@ void StakeTransactionProcessor::init_storages_impl() m_blockchain_based_list.reset(new BlockchainBasedList(m_config_dir + "/" + BLOCKCHAIN_BASED_LIST_FILE_NAME, first_block_number)); } -void StakeTransactionProcessor::process_block_stake_transaction(uint64_t block_index, const block& block, const crypto::hash& block_hash, bool update_storage) +namespace { - if (block_index <= m_storage->get_last_processed_block_index()) + +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 && m_blockchain.nettype() != FAKECHAIN) + { + 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(b_hash, bbl_idxs, bbqs_idxs, qcl_idxs); + Ids bbqs = fromIndexes(tiers, bbqs_idxs); + Ids qcl = fromIndexes(tiers, qcl_idxs); + +#if 0 + { + std::ostringstream oss; + for(auto& it : bbqs) { oss << epee::string_tools::pod_to_hex(it) << "\n"; } + MWARNING("BBQS: height ") << disq_extra.item.block_height << ENDL << oss.str(); + } + { + std::ostringstream oss; + for(auto& it : qcl) { oss << epee::string_tools::pod_to_hex(it) << "\n"; } + MWARNING("QCL: height ") << disq_extra.item.block_height << ENDL << oss.str(); + } +#endif + + 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 && m_blockchain.nettype() != FAKECHAIN) + { + 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)); +} - if (m_blockchain.get_hard_fork_version(block_index) >= config::graft::STAKE_TRANSACTION_PROCESSING_DB_VERSION) +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)) { - //analyze block transactions and add new stake transactions if exist + 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; - stake_transaction stake_tx; + 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()) + return; + + if (block_index >= m_active_height) + { + //analyze block transactions and add new stake transactions if exist std::vector txs; std::vector missed_txs; @@ -176,12 +428,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 +519,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 @@ -295,7 +566,7 @@ void StakeTransactionProcessor::synchronize() uint64_t height = m_blockchain.get_current_blockchain_height(); - if (!height || m_blockchain.get_hard_fork_version(height - 1) < config::graft::STAKE_TRANSACTION_PROCESSING_DB_VERSION) + if (!height || height < m_active_height) return; if (!m_storage || !m_blockchain_based_list) @@ -418,7 +689,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 +737,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; } @@ -492,12 +768,7 @@ void StakeTransactionProcessor::invoke_update_blockchain_based_list_handler(bool invoke_update_blockchain_based_list_handler_impl(depth); } -void StakeTransactionProcessor::set_enabled(bool arg) -{ - m_enabled = arg; -} - bool StakeTransactionProcessor::is_enabled() const { - return m_enabled; + return m_active_height < CRYPTONOTE_MAX_BLOCK_NUMBER; } diff --git a/src/cryptonote_core/stake_transaction_processor.h b/src/cryptonote_core/stake_transaction_processor.h index 5d8bff0a7..50e38344a 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&); @@ -43,11 +44,21 @@ class StakeTransactionProcessor /// Force invoke update handler for blockchain based list void invoke_update_blockchain_based_list_handler(bool force = true, size_t depth = 1); - /// Turns on/off processing - void set_enabled(bool arg); + // checks if stake tx procesing enabled + bool is_enabled() const; + /// activate stake processing from specific height + void set_active_from_height(uint64_t height) { m_active_height = height; } - bool is_enabled() const; + /// return height stake processing active from + uint64_t active_from_height() const { return m_active_height; } + + StakeTransactionStorage * get_storage() const { return m_storage.get(); } + BlockchainBasedList * get_blockchain_based_list() const { return m_blockchain_based_list.get(); } + + /// 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(); @@ -57,17 +68,22 @@ 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; std::unique_ptr m_storage; std::unique_ptr m_blockchain_based_list; + // TODO: move lock to storage? mutable epee::critical_section m_storage_lock; supernode_stakes_update_handler m_on_stakes_update; blockchain_based_list_update_handler m_on_blockchain_based_list_update; bool m_stakes_need_update; bool m_blockchain_based_list_need_update; bool m_enabled {true}; + uint64_t m_active_height {1}; }; } 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 diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index 1ac0e7864..167261a7f 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -42,7 +42,8 @@ set(core_tests_sources tx_validation.cpp v2_tests.cpp rct.cpp - bulletproofs.cpp) + bulletproofs.cpp + rta_tests.cpp) set(core_tests_headers block_reward.h @@ -60,7 +61,8 @@ set(core_tests_headers tx_validation.h v2_tests.h rct.h - bulletproofs.h) + bulletproofs.h + rta_tests.h) add_executable(core_tests ${core_tests_sources} diff --git a/tests/core_tests/README.md b/tests/core_tests/README.md new file mode 100644 index 000000000..68cff77bb --- /dev/null +++ b/tests/core_tests/README.md @@ -0,0 +1,83 @@ +# How to use 'core-tests' framework + +## Steps needed to create another test case; + +1. create a struct as a subclass `test_chain_unit_base` class, implement it in "my_test.h|cpp" files, + e.g.: + + ``` + struct gen_rta_tests : public test_chain_unit_base + { + gen_rta_tests(); + + // test generator method: here we define the test sequence + bool generate(std::vector& events) const; + + + bool check1(cryptonote::core& c, size_t ev_index, const std::vector& events); + bool check_stake_registered(cryptonote::core& c, size_t ev_index, const std::vector& events); + }; + ``` + + + 1.1. implement test sequence in `bool generate(std::vector& events) const;` method; + + 1.2. "check" functions should follow the interface: + ``` + bool check_function(cryptonote::core& c, size_t ev_index, const std::vector& events); + ``` + + 1.3. register "check" functions with `REGISTER_CALLBACK_METHOD` macro invoked in constructor: + ``` + REGISTER_CALLBACK_METHOD(gen_rta_tests, check_function); + ``` + + +2. add + + ``` + #include "your_test.h" to the "chaingen_test_list.h" + ``` + +3. add + + ``` + GENERATE_AND_PLAY(gen_rta_tests); + ``` + + to the `chaingen_main.cpp` inside + + ``` + else if (command_line::get_arg(vm, arg_generate_and_play_test_data)) + { + ``` + + block + + +4. (optional) define a `gen_test_options` struct representing testing hardfork table, which will be passed to cryptonote::core instance, e.g.: + + ``` + template<> + struct get_test_options { + const std::pair hard_forks[4] = {std::make_pair(1, 0), std::make_pair(13, 14), std::make_pair(14, 73)}; + const cryptonote::test_options test_options = { + hard_forks + }; + }; + ``` + + +5. implement `bool generate(std::vector& events) const;` method where you place your test flow. Your job is to generate blocks and transactions which will be played through 'cryptonote::core' instance; +Blocks and transactions to be added to the `events` vector. Normally it added by macros so you don't have to add it manually. + + 5.1. use `MAKE_GENESIS_BLOCK` macro to make a genesis block and add it the chain + 5.2. use `MAKE_NEXT_BLOCK` macro to create and add block to the chain + 5.3. use `construct_tx_with_fee` function to create transaction + 5.4. use `MAKE_NEXT_BLOCK_TX1` macro to add transaction to a block and add block to the chain + +6. schedule a "check" function call at the specific blockchain state inside `generate` method + ``` + DO_CALLBACK(events, "check_function"); + ``` + diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index d3cb52246..e18512231 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -567,6 +567,21 @@ bool construct_tx_to_key(const std::vector& events, cryptonote return construct_tx(from.get_keys(), sources, destinations, from.get_keys().m_account_address, std::vector(), tx, 0); } + +bool construct_stake_tx_to_key(const std::vector &events, transaction &tx, const block &blk_head, const account_base &from, const account_base &to, + uint64_t amount, uint64_t fee, size_t nmix, + const public_key &supernode_id, const signature &supernode_sign, uint64_t unlock_time) +{ + vector sources; + vector destinations; + fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations); + std::vector extra; + add_graft_stake_tx_extra_to_extra(extra, epee::string_tools::pod_to_hex(supernode_id), + to.get_keys().m_account_address, supernode_sign); + return construct_tx(from.get_keys(), sources, destinations, from.get_keys().m_account_address, extra, tx, unlock_time); +} + + transaction construct_tx_with_fee(std::vector& events, const block& blk_head, const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee) { @@ -576,6 +591,17 @@ transaction construct_tx_with_fee(std::vector& events, const b return tx; } +transaction construct_stake_tx_with_fee(std::vector& events, const block& blk_head, + const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee, + const public_key &supernode_id, const signature &supernode_sign, uint64_t unlock_time) +{ + transaction tx; + construct_stake_tx_to_key(events, tx, blk_head, acc_from, acc_to, amount, fee, 0, supernode_id, supernode_sign, unlock_time); + events.push_back(tx); + return tx; +} + + uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx) { uint64_t res = 0; std::map > outs; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 6b9277a30..30b784a20 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -225,10 +225,28 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins bool construct_tx_to_key(const std::vector& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix); + +bool construct_stake_tx_to_key(const std::vector& events, cryptonote::transaction& tx, + const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t fee, size_t nmix, + const crypto::public_key &supernode_id, + const crypto::signature &supernode_sign, + uint64_t unlock_time); + + + cryptonote::transaction construct_tx_with_fee(std::vector& events, const cryptonote::block& blk_head, const cryptonote::account_base& acc_from, const cryptonote::account_base& acc_to, uint64_t amount, uint64_t fee); + +cryptonote::transaction construct_stake_tx_with_fee(std::vector& events, const cryptonote::block& blk_head, + const cryptonote::account_base& acc_from, const cryptonote::account_base& acc_to, + uint64_t amount, uint64_t fee, + const crypto::public_key &supernode_id, + const crypto::signature &supernode_sign, + uint64_t unlock_time); + void get_confirmed_txs(const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs); bool find_block_chain(const std::vector& events, std::vector& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); void fill_tx_sources_and_destinations(const std::vector& events, const cryptonote::block& blk_head, diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 576187db8..19eec60d7 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -42,6 +42,7 @@ namespace const command_line::arg_descriptor arg_generate_test_data = {"generate_test_data", ""}; const command_line::arg_descriptor arg_play_test_data = {"play_test_data", ""}; const command_line::arg_descriptor arg_generate_and_play_test_data = {"generate_and_play_test_data", ""}; + const command_line::arg_descriptor arg_generate_and_play_rta_tests_only = {"generate_and_play_rta_tests_only", ""}; const command_line::arg_descriptor arg_test_transactions = {"test_transactions", ""}; const command_line::arg_descriptor arg_filter = { "filter", "Regular expression filter for which tests to run" }; } @@ -62,6 +63,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_options, arg_generate_test_data); command_line::add_arg(desc_options, arg_play_test_data); command_line::add_arg(desc_options, arg_generate_and_play_test_data); + command_line::add_arg(desc_options, arg_generate_and_play_rta_tests_only); command_line::add_arg(desc_options, arg_test_transactions); command_line::add_arg(desc_options, arg_filter); @@ -95,6 +97,11 @@ int main(int argc, char* argv[]) { PLAY("chain001.dat", gen_simple_chain_001); } + else if (command_line::get_arg(vm, arg_generate_and_play_rta_tests_only)) + { + GENERATE_AND_PLAY(gen_rta_test); + GENERATE_AND_PLAY(gen_rta_disqualification_test); + } else if (command_line::get_arg(vm, arg_generate_and_play_test_data)) { GENERATE_AND_PLAY(gen_simple_chain_001); @@ -250,6 +257,11 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_bp_tx_invalid_wrong_amount); GENERATE_AND_PLAY(gen_bp_tx_invalid_borromean_type); + // RTA tests + GENERATE_AND_PLAY(gen_rta_test); + GENERATE_AND_PLAY(gen_rta_disqualification_test); + + el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error); MLOG(level, "\nREPORT:"); MLOG(level, " Test run: " << tests_count); diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index c12e97f95..59c3f6cfb 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -43,6 +43,7 @@ #include "rct.h" #include "multisig.h" #include "bulletproofs.h" +#include "rta_tests.h" /************************************************************************/ /* */ /************************************************************************/ diff --git a/tests/core_tests/rta_tests.cpp b/tests/core_tests/rta_tests.cpp new file mode 100644 index 000000000..013d4b83a --- /dev/null +++ b/tests/core_tests/rta_tests.cpp @@ -0,0 +1,763 @@ +// Copyright (c) 2019, Graft Project +// 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 "chaingen.h" +#include "rta_tests.h" +#include "graft_rta_config.h" +#include "utils/sample_generator.h" +#include "serialization/binary_utils.h" +#include + +using namespace epee; +using namespace cryptonote; + +namespace +{ +// for future use +const std::pair supernode_keys[] = { +{ "6efc0beb23adbe47b762350ec58e7d5054164b900af1d24bb77456b98062a906", + "f4957a1914119a20106b94fd88f25dd073f452a5199d493807263169c5b9db0b" }, +{ "4ae19dcd25fc3b023cc9ff168df0e5057ad3a6f12571707a455d0929ee35ee18", + "c7e61be8c0fe0198869dd6515b84b0075c962d5fb597d2cadd724a180ce39307" }, +{ "90b39f1406a53bddbc3e78883b418e75ecd4c9f8000a0b53ae881dcc4099f3b7", + "e02313e01503f1e7398f904fdcf1ab3ac4616161df4d7b8fd8cd3e7d0587b409" }, +{ "5a8786b55979d5c5b0b2cc162354f00c39d916583d2b97fe9426a99d1c473932", + "ebcab40d42a47cf52e5deb7f9aa27f7d92913f32644e5b3f38ac436cb32c2d00" }, +{ "ef842787b6733ca14de77db5f3f3a6a1c97e1bc9f046f57d3ed3b19484c541ea", + "1c78b4f2b33b51ba62595dbcc8225dfddd31afbdcdc9c99f4578433c2e6f4f05" }, +{ "be01835610894c1deb35160b683b7e267f93cde3d5a7ccfdcec8e9450c5ea22e", + "bd9b1ca21340093cc7802f7c69323b3079a3b2b9e248d6313b09e664d15bd40c" }, +{ "ad07ebf9a0e68a0333c76f12c2231bb04666d2773c8a7926562bf4e01e369111", + "994f8f0b9cf31591f8e587eb2af7c2be6bc53624e5dbfc6473758bc1027e360d" }, +{ "e8f00430416fa44aa032d0134e2c5f349a917d0f6330ae3ebe9a90586ce152db", + "17cbe82211fa7eca514b78965ceb9981a85d25fe7e0f516ead4509fc94034804" }, +{ "328430dfe107a6086239016b7d40b8b2c9856f6cb067326e77a96870475794fc", + "898498cac3a0ed7c84ca17dbf58777c05687f576220d7286fcaf9016de24b20c" }, +{ "0a84b00abe165eccea53064392202f1ea2df572a5b2adc18dcd20940011df5c1", + "d22b0808cfaa4fccf05762372aaf2063b047ed02374367d07c04eca57633a400" }, +{ "13a6cb678edec88e32b001b1959d2ac545b682156301db1710d07beb806a6d66", + "5eaa4be628b0112c1622711c956e5c5b1dd2bb8f7f6a7be55c3ae2b09d063308" }, +{ "e5d9b010fff2f8570eead1ebdd640663816c6cdafde114a9ba6c7154da7b867e", + "2db7f9bb92d9f768207ea8532b21c39b08c62baa544e8c1d5985bc32697e2d05" }, +{ "33134b18123ba123d7b394ef73f8a84d11e9442cf09017c8bbbb94fd24ab2a04", + "a71398ba703a9ec7b4ee7e4456ff287e1a091901b5789cf74ec55d265525a10b" }, +{ "dd6803f71150d96a3e5a02abfa014547e7e23f10a4d04d48d35288760d91d6b8", + "4265ff3b0f55b18a4cfa1591c89a09ac9c2d2a4a5ec062e02fde2714c0585c0e" }, +{ "ebb359bdac313ed773df8e79dd6fa05f2cac10e1fc47de998bc338eec001288b", + "49d71ad2603ca2924c0187096fe00cf4e261caff5922b9ff33b9f8606163d00a" }, +{ "688b66d57adeb2c8f07bb46b975ac94d90db82096cae5b2c52f3f02f1d3d6243", + "ea333672055c6133cbbabff55dd3482f19b863eba29d90effc5cc2ab71e1bd04" }, +}; + +std::vector g_supernode_list; + +} + +//To provide the same sorting order of sns, supernode_keys is used. It is required for the gen_rta_disqualification_test +Supernode::Supernode(int idx) +{ + account.generate(); + string_tools::hex_to_pod(supernode_keys[idx].first, keys.pkey); + string_tools::hex_to_pod(supernode_keys[idx].second, keys.skey); +} + +/////////////////////////////////////////// +/// gen_rta_disqualification_test +/// + +//Note, ctor of a test is called two times. First time to call generate that collects events and so on. Second time to perform events. +//The workaround for this is using global variables. + +std::deque single_callbacks; + +void gen_rta_disqualification_test::set_single_callback(std::vector& events, const single_callback_t& func) const +{ + single_callbacks.push_back(func); + DO_CALLBACK(events, "call_single_callback"); +} + +bool gen_rta_disqualification_test::call_single_callback(cryptonote::core& c, size_t ev_index, const std::vector& events) +{ + assert(!single_callbacks.empty()); + bool res = single_callbacks.front()(c, ev_index, events); + single_callbacks.pop_front(); + return res; +} + +gen_rta_disqualification_test::gen_rta_disqualification_test() +{ + REGISTER_CALLBACK_METHOD(gen_rta_disqualification_test, call_single_callback); +} + +int get_height(std::vector& events) +{ + int i = 0; + for(auto &e : events) { if(e.which() != 0) continue; ++i; } + return i; +} + +crypto::hash get_block_hash(std::vector& events, int blk_idx) +{ + int i = 0; + for(auto &e : events) + { + if(e.which() != 0) continue; + if(++i == blk_idx) + return boost::get(e).hash; + } + assert(false); + return crypto::hash(); +} + +cryptonote::transaction gen_rta_disqualification_test::make_disqualification1_transaction(std::vector& events, std::vector>& tiers, int disq_sn_idx) const +{ + std::string str = "something"; + crypto::hash block_hash; //disq_extra.item.block_hash + crypto::cn_fast_hash(str.data(), str.size(), block_hash); + + std::vector bbqs, qcl; + bool res = graft::generator::select_BBQS_QCL(block_hash, tiers, bbqs, qcl); + assert(res); + + int height = get_height(events); + + { + std::ostringstream oss; + for(auto i : bbqs) { oss << epee::string_tools::pod_to_hex(g_supernode_list[i].keys.pkey) << "\n"; } + MWARNING("BBQS: height ") << height << ENDL << oss.str(); + } + { + std::ostringstream oss; + for(auto i : qcl) { oss << epee::string_tools::pod_to_hex(g_supernode_list[i].keys.pkey) << "\n"; } + MWARNING("QCL: height ") << height << ENDL << oss.str(); + } + + //find index in qcl of disq_sn_idx + int disq_qcl_idx = -1; + for(size_t i = 0; i < qcl.size(); ++i) + { + if(qcl[i] != disq_sn_idx) continue; + disq_qcl_idx = i; + break; + } + assert(0 <= disq_qcl_idx); + + //create extra for disqualification1 + std::vector extra; + { + tx_extra_graft_disqualification disq; + { + disq.item.block_height = height - 5; + disq.item.block_hash = get_block_hash(events, disq.item.block_height); + disq.item.id = g_supernode_list[disq_sn_idx].keys.pkey; + } + crypto::hash hash; + { + std::string item_str; + ::serialization::dump_binary(disq.item, item_str); + crypto::cn_fast_hash(item_str.data(), item_str.size(), hash); + } + for(int i = 0, ei = bbqs.size(); i < ei; ++i) + { + if(bbqs[i] == disq_sn_idx) continue; + Supernode& sn = g_supernode_list[bbqs[i]]; + tx_extra_graft_disqualification::signer_item si; + si.signer_id = sn.keys.pkey; + MDEBUG("signed id = " ) << epee::string_tools::pod_to_hex(si.signer_id);; + crypto::generate_signature(hash, sn.keys.pkey, sn.keys.skey, si.sign); + disq.signers.emplace_back(std::move(si)); + } + + extra.push_back(TX_EXTRA_GRAFT_DISQUALIFICATION_TAG); + std::string disq_str; + ::serialization::dump_binary(disq, disq_str); + std::copy(disq_str.begin(), disq_str.end(), std::back_inserter(extra)); + } + + transaction tx; + tx.type = 0; + tx.version = 123; + tx.extra = extra; + + events.push_back(tx); + + return tx; +} + +cryptonote::transaction gen_rta_disqualification_test::make_disqualification2_transaction(std::vector& events, std::vector>& tiers, std::vector disq_sn_idxs) const +{ + std::string str = "something"; + crypto::hash block_hash; + crypto::cn_fast_hash(str.data(), str.size(), block_hash); + + std::string payment_id = "payment_id " + std::to_string(++tmp_payment_idx); + + std::vector auths; + bool res = graft::generator::select_AuthSample(payment_id, tiers, auths); + assert(res); + + //check that all disq_sn_idxs in auths + for(auto& idx : disq_sn_idxs) + { + auto it = std::find(auths.begin(), auths.end(), idx); + if(it == auths.end()) + { + MDEBUG("error, disq_sn_idxs index ") << idx << " is not in auths"; + } + assert(it != auths.end()); + } + + int height = get_height(events); + + //create extra for disqualification2 + std::vector extra; + { + tx_extra_graft_disqualification2 disq; + { + disq.item.payment_id = payment_id; + disq.item.block_height = height - 5; // - 10; + disq.item.block_hash = get_block_hash(events, disq.item.block_height); + for(auto& idx : disq_sn_idxs) + { + disq.item.ids.push_back(g_supernode_list[idx].keys.pkey); + } + } + crypto::hash hash; + { + std::string item_str; + ::serialization::dump_binary(disq.item, item_str); + crypto::cn_fast_hash(item_str.data(), item_str.size(), hash); + } + for(int i = 0, ei = auths.size(); i < ei; ++i) + { + if(std::find(disq_sn_idxs.begin(), disq_sn_idxs.end(), auths[i]) != disq_sn_idxs.end()) continue; + Supernode& sn = g_supernode_list[auths[i]]; + tx_extra_graft_disqualification2::signer_item si; + si.signer_id = sn.keys.pkey; + MDEBUG("signed id = " ) << epee::string_tools::pod_to_hex(si.signer_id);; + crypto::generate_signature(hash, sn.keys.pkey, sn.keys.skey, si.sign); + disq.signers.emplace_back(std::move(si)); + } + + extra.push_back(TX_EXTRA_GRAFT_DISQUALIFICATION2_TAG); + std::string disq_str; + ::serialization::dump_binary(disq, disq_str); + std::copy(disq_str.begin(), disq_str.end(), std::back_inserter(extra)); + } + + transaction tx; + tx.type = 0; + tx.version = 124; + tx.extra = extra; + + events.push_back(tx); + + return tx; +} + +#if 0 +//useful for debug; put 'set_single_callback(events, my_stop);' into generate and set breakpoint here +bool my_stop(cryptonote::core& c, size_t ev_index, const std::vector& events) +{ + return true; +} +#endif + +bool gen_rta_disqualification_test::generate(std::vector& events) const +{ + assert(single_callbacks.empty()); + {//init g_supernode_list + g_supernode_list.clear(); + constexpr size_t LIST_SIZE = sizeof(supernode_keys)/sizeof(supernode_keys[0]); + for (size_t i = 0; i < LIST_SIZE; ++i) { + g_supernode_list.push_back(Supernode(i)); + } + } + + uint64_t ts_start = 1338224400; + + // create one account + GENERATE_ACCOUNT(miner); + // create another account + GENERATE_ACCOUNT(alice); + // generate genesis block + MAKE_GENESIS_BLOCK(events, blk_0, miner, ts_start); // height = 1 + // create another account and add it to 'events' + MAKE_ACCOUNT(events, miner0); + MAKE_ACCOUNT(events, alice0); + + // generate one block and add it to the chain + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner0); // height = 2 + // MAKE_NEXT_BLOCK(events, blk_1_side, blk_0, miner0); // alternative chain + MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner0); // height = 3 + //MAKE_TX(events, tx_0, first_miner_account, alice, 151, blk_2); + + +#if 0 + //// how to send multiple txes in one block + REWIND_BLOCKS(events, blk_2r, blk_2, miner0); + MAKE_TX_LIST_START(events, txlist_0, miner0, alice0, MK_COINS(1), blk_2); + MAKE_TX_LIST(events, txlist_0, miner0, alice0, MK_COINS(2), blk_2); + MAKE_TX_LIST(events, txlist_0, miner0, alice0, MK_COINS(4), blk_2); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner0, txlist_0); + REWIND_BLOCKS(events, blk_3r, blk_3, miner0); + MAKE_TX(events, tx_1, miner0, alice0, MK_COINS(50), blk_3); + DO_CALLBACK(events, "verify_callback_1"); + return true; + //// +#endif + // mine N blocks + REWIND_BLOCKS_N(events, blk_3, blk_2, miner0, 60); // height = 63 + + cryptonote::block last_blk; + {//it is a trick to make many outputs with enough coins in them + cryptonote::block top_blk = blk_3; + + std::list stake_txes; + uint64_t coinss[] = {MK_COINS(50000000), MK_COINS(40000000), MK_COINS(30000000), MK_COINS(5000000)}; + for(int idx = 0, cnt = 1; idx < 4; ++idx, cnt*=2) + { + for(int i = 0; i < cnt; ++i) + { + transaction tx_0(construct_tx_with_fee(events, top_blk, miner0, miner0, coinss[idx], TESTS_DEFAULT_FEE)); + stake_txes.push_back(tx_0); + } + MAKE_NEXT_BLOCK_TX_LIST(events, next_blk, top_blk, miner0, stake_txes); + top_blk = next_blk; + stake_txes.clear(); + } + last_blk = top_blk; + } + + {//fix "One of outputs for one of inputs has wrong tx.unlock_time = + REWIND_BLOCKS_N(events, blk, last_blk, miner0, 120); last_blk = blk; + } + + std::vector> tiers(4); + //sn idx to age + std::map sn2age; + + //generate stakes + { + int b_height = 164; + MDEBUG("b_height should be " ) << get_height(events); //187 + + + struct item_stake + { + int blk, sn_idx, tier; + int blk_duration; + }; + + item_stake stake_table[] = { + { 0, 0, 0, 70 }, + { 0, 1, 0, 70 }, + { 1, 2, 0, 70 }, + { 1, 3, 0, 70 }, + { 1, 4, 1, 70 }, + { 1, 5, 1, 70 }, + { 3, 6, 1, 70 }, + { 3, 7, 2, 70 }, + { 3, 8, 2, 70 }, + { 3, 9, 2, 100 }, + { 4, 10, 3, 100 }, + { 4, 11, 3, 100 }, + }; + + const int stake_table_cnt = sizeof(stake_table)/sizeof(stake_table[0]); + + cryptonote::block l_blk = last_blk; + + std::list txes; + int st_idx = 0; + for(int i = 0; st_idx < stake_table_cnt ; ++i) //i is block index + { + assert(st_idx == stake_table_cnt || i <= stake_table[st_idx].blk); + for(; st_idx < stake_table_cnt && i == stake_table[st_idx].blk; ++st_idx) + { + const item_stake& is = stake_table[st_idx]; + Supernode& sn = g_supernode_list[is.sn_idx]; + assert(0 <= is.tier && is.tier <= 3); + namespace cg = config::graft; + uint64_t coins = (is.tier == 0)? cg::TIER1_STAKE_AMOUNT : (is.tier == 1)? cg::TIER2_STAKE_AMOUNT : (is.tier == 2)? cg::TIER3_STAKE_AMOUNT : cg::TIER4_STAKE_AMOUNT; + tiers[is.tier].push_back(is.sn_idx); + assert(sn2age.find(is.sn_idx) == sn2age.end()); + sn2age[is.sn_idx] = i; + // create stake transaction + transaction tx(construct_stake_tx_with_fee(events, last_blk, miner0, sn.account, coins, TESTS_DEFAULT_FEE, + sn.keys.pkey, sn.signature(), b_height + i + is.blk_duration)); + txes.push_back(tx); + } + + if(!txes.empty()) + { + MAKE_NEXT_BLOCK_TX_LIST(events, next_blk, l_blk, miner0, txes); + l_blk = next_blk; + txes.clear(); + } + else + {//empty block + MAKE_NEXT_BLOCK(events, next_blk, l_blk, miner0); + l_blk = next_blk; + } + } + last_blk = l_blk; + } + + MDEBUG("stake tx list constructed"); + {//sort valid supernodes by the age of stake + for(auto& tier : tiers) + { + std::sort(tier.begin(), tier.end(), [&sn2age](int i1, int i2) + { + return sn2age[i1] < sn2age[i2] || (sn2age[i1] == sn2age[i2] && epee::string_tools::pod_to_hex(g_supernode_list[i1].keys.pkey) < epee::string_tools::pod_to_hex(g_supernode_list[i2].keys.pkey)); + }); + } + } + + { REWIND_BLOCKS_N(events, blk, last_blk, miner0, 15); last_blk = blk; } + check_bbl_cnt("point 1", events, 12, 0); + check_bbl_cnt("point 2", events, 12, 5); + + //generate disqualifications + { + struct item_disq + { + int blk; + int disq_type; //1 or 2 + std::vector sn_idxs; //must be single for disq_type 1 + }; + + item_disq disq_table[] = { + { 0, 1, {5} }, + { 0, 1, {9} }, + { 1, 2, {1, 11}}, + { 1, 1, {8} }, + { 2, 2, {0}}, + }; + + const int disq_table_cnt = sizeof(disq_table)/sizeof(disq_table[0]); + + cryptonote::block l_blk = last_blk; + + std::list txes; + int dq_idx = 0; + for(int i = 0; dq_idx < disq_table_cnt ; ++i) //i is block index + { + assert(dq_idx == disq_table_cnt || i <= disq_table[dq_idx].blk); + for(; dq_idx < disq_table_cnt && i == disq_table[dq_idx].blk; ++dq_idx) + { + const item_disq& iq = disq_table[dq_idx]; + assert(iq.disq_type == 1 || iq.disq_type == 2); + transaction tx; + if(iq.disq_type == 1) + { + assert(iq.sn_idxs.size() == 1); + tx = make_disqualification1_transaction(events, tiers, iq.sn_idxs[0]); + } + else + { + assert(!iq.sn_idxs.empty()); + tx = make_disqualification2_transaction(events, tiers, iq.sn_idxs); + } + txes.push_back(tx); + } + + if(!txes.empty()) + { + MAKE_NEXT_BLOCK_TX_LIST(events, next_blk, l_blk, miner0, txes); + l_blk = next_blk; + txes.clear(); + } + else + {//empty block + MAKE_NEXT_BLOCK(events, next_blk, l_blk, miner0); + l_blk = next_blk; + } + } + last_blk = l_blk; + } + + { REWIND_BLOCKS_N(events, blk, last_blk, miner0, 7); last_blk = blk; } + + check_bbl_cnt("point 3", events, 6); + check_bbl_cnt("point 4", events, 6, 5); + + {//it is assumed DISQUALIFICATION_DURATION_BLOCK_COUNT === DISQUALIFICATION2_DURATION_BLOCK_COUNT === 10 + REWIND_BLOCKS_N(events, blk, last_blk, miner0, DISQUALIFICATION_DURATION_BLOCK_COUNT - 7 - 1); last_blk = blk; + } + + check_bbl_cnt("point 5", events, 8); //two disqualifications has been expired + check_bbl_cnt("point 6", events, 6, 5); + + { REWIND_BLOCKS_N(events, blk, last_blk, miner0, 1); last_blk = blk; } + + check_bbl_cnt("point 7", events, 11); //+ three disqualifications expired + check_bbl_cnt("point 8", events, 6, 5); + + { REWIND_BLOCKS_N(events, blk, last_blk, miner0, 1); last_blk = blk; } + + check_bbl_cnt("point 9", events, 12); //all disqualifications expired + check_bbl_cnt("point 10", events, 8, 2); //two blocks back shoult be the same + + {// rewind for 'STAKE_PERIOD' blocks + //config::graft::TRUSTED_RESTAKING_PERIOD === 6 + REWIND_BLOCKS_N(events, blk, last_blk, miner0, 54); last_blk = blk; + } + + check_bbl_cnt("point 11", events, 0, 0); + check_bbl_cnt("point 12", events, 2, 1); //two last stakes, see stake_table for expired stakes + + MDEBUG("last block height " ) << get_height(events); //275, b_height(164) + 4 + 100 + TRUSTED_RESTAKING_PERIOD(6) = 274 + + return true; +} + +bool gen_rta_disqualification_test::check_bbl_cnt(cryptonote::core& c, int expected_cnt, uint64_t depth, const std::string& context) const +{ + std::string ctx = "gen_rta_disqualification_test::check_bbl_cnt " + context; + DEFINE_TESTS_ERROR_CONTEXT(ctx.c_str()); + StakeTransactionProcessor * stp = c.get_stake_tx_processor(); + MDEBUG("check for blockchain height: ") << c.get_current_blockchain_height() - depth << " expected count = " << expected_cnt; + + const auto & tiers = stp->get_blockchain_based_list()->tiers(depth);; + int cnt = 0; + for(auto& tier : tiers){ cnt += tier.size(); } + CHECK_EQ(cnt, expected_cnt); + return true; +} + +void gen_rta_disqualification_test::check_bbl_cnt(const std::string& context, std::vector& events, int expected_cnt, uint64_t depth) const +{ + set_single_callback(events, [this, context, expected_cnt, depth](cryptonote::core& c, size_t ev_index, const std::vector& events)->bool + { + return check_bbl_cnt(c, expected_cnt, depth, context); + }); +} + +/////////////////////////////////////////// +/// gen_rta_test +/// + +gen_rta_test::gen_rta_test() +{ + REGISTER_CALLBACK_METHOD(gen_rta_test, check_stake_registered); + REGISTER_CALLBACK_METHOD(gen_rta_test, check_stake_expired); +} + +bool gen_rta_test::generate(std::vector& events) const +{ + {//init g_supernode_list + g_supernode_list.clear(); + constexpr const size_t LIST_SIZE = 2; + for (size_t i = 0; i < LIST_SIZE; ++i) { + g_supernode_list.push_back(Supernode()); + } + } + + uint64_t ts_start = 1338224400; + + // create one account + GENERATE_ACCOUNT(miner); + // create another account + GENERATE_ACCOUNT(alice); + // generate genesis block + MAKE_GENESIS_BLOCK(events, blk_0, miner, ts_start); // height = 1 + // create another account and add it to 'events' + MAKE_ACCOUNT(events, miner0); + MAKE_ACCOUNT(events, alice0); + + // generate one block and add it to the chain + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner0); // height = 2 + // MAKE_NEXT_BLOCK(events, blk_1_side, blk_0, miner0); // alternative chain + MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner0); // height = 3 + //MAKE_TX(events, tx_0, first_miner_account, alice, 151, blk_2); + + +#if 0 + //// how to send multiple txes in one block + REWIND_BLOCKS(events, blk_2r, blk_2, miner0); + MAKE_TX_LIST_START(events, txlist_0, miner0, alice0, MK_COINS(1), blk_2); + MAKE_TX_LIST(events, txlist_0, miner0, alice0, MK_COINS(2), blk_2); + MAKE_TX_LIST(events, txlist_0, miner0, alice0, MK_COINS(4), blk_2); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner0, txlist_0); + REWIND_BLOCKS(events, blk_3r, blk_3, miner0); + MAKE_TX(events, tx_1, miner0, alice0, MK_COINS(50), blk_3); + DO_CALLBACK(events, "verify_callback_1"); + return true; + //// +#endif + // mine N blocks + REWIND_BLOCKS_N(events, blk_3, blk_2, miner0, 60); // height = 63 + //cryptonote::block blk_3 = blk_2; + + // create transaction + transaction tx_0(construct_tx_with_fee(events, blk_3, miner0, alice0, MK_COINS(1000), TESTS_DEFAULT_FEE)); + // add it to the new block followed by 'blk_3', mined by 'miner0' + MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner0, tx_0); // height = 64 + + const size_t STAKE_PERIOD = 100; + // deposit stakes + cryptonote::block prev_block; + std::list stake_txes; + for (Supernode & sn : g_supernode_list) { + // create stake transaction + transaction tx(construct_stake_tx_with_fee(events, blk_4, miner0, sn.account, MK_COINS(50000), TESTS_DEFAULT_FEE, + sn.keys.pkey, sn.signature(), 64 + STAKE_PERIOD)); + stake_txes.push_back(tx); +// MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4, miner0, tx); // height = 64 +// REWIND_BLOCKS_N(events, blk_6, blk_5, miner0, config::graft::STAKE_VALIDATION_PERIOD); // height = 70; +// prev_block = blk_6; + } + MAKE_NEXT_BLOCK_TX_LIST(events, blk_5, blk_4, miner0, stake_txes); + + MDEBUG("stake tx list constructed"); + + + REWIND_BLOCKS_N(events, blk_6, blk_5, miner0, config::graft::STAKE_VALIDATION_PERIOD); // height = 70; + // schedule a 'check_stake_registered' check (checking if stake is registered) + DO_CALLBACK(events, "check_stake_registered"); + + // rewind for 'STAKE_PERIOD' blocks + REWIND_BLOCKS_N(events, bkl_7, blk_6, miner0, STAKE_PERIOD /* + config::graft::TRUSTED_RESTAKING_PERIOD*/); // TODO: check why TRUSTED_RESTAKING_PERIOD is not applied + + // schedule a 'check_stake_expired' check (checking if stake is expired) + DO_CALLBACK(events, "check_stake_expired"); + return true; +} + +// Not used, just to show how to get balances +bool gen_rta_test::check1(core &c, size_t ev_index, const std::vector &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_rta_test::check1"); + cryptonote::account_base miner0 = boost::get(events[1]); + cryptonote::account_base alice0 = boost::get(events[2]); + std::vector chain; + map_hash2tx_t mtx; + std::vector block_list; + bool r = c.get_blocks(0, 5 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + + /*bool r = */find_block_chain(events, chain, mtx, get_block_hash(block_list.back())); + + MDEBUG("chain size: " << chain.size()); + MDEBUG("chain height (core): " << c.get_current_blockchain_height()); + MDEBUG("events size: " << events.size()); + MDEBUG("ev_index: " << ev_index); + MDEBUG("miner BALANCE: " << print_money(get_balance(miner0, chain, mtx))); + MDEBUG("alice BALANCE: " << print_money(get_balance(alice0, chain, mtx))); + + return true; +} + + +bool gen_rta_test::check_stake_registered(core &c, size_t ev_index, const std::vector &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_rta_test::check_stake_registered"); + cryptonote::account_base miner0 = boost::get(events[1]); + cryptonote::account_base alice0 = boost::get(events[2]); + std::vector chain; + map_hash2tx_t mtx; + std::vector block_list; + bool r = c.get_blocks(0, 5 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + + /*bool r = */find_block_chain(events, chain, mtx, get_block_hash(block_list.back())); + + for (const auto & tx : mtx) { + MDEBUG("tx: " << tx.first); + std::string supernode_public_id; + cryptonote::account_public_address supernode_public_address; + crypto::signature supernode_signature; + crypto::secret_key tx_secret_key; + bool is_stake_tx = get_graft_stake_tx_extra_from_extra(*tx.second, + supernode_public_id, + supernode_public_address, + supernode_signature, + tx_secret_key); + if (is_stake_tx) { + MDEBUG(" is stake tx for id: " << supernode_public_id); + } + } + + StakeTransactionProcessor * stp = c.get_stake_tx_processor(); + MDEBUG("blockchain height: " << c.get_current_blockchain_height()); + + CHECK_NOT_EQ(stp->get_blockchain_based_list(), 0); + CHECK_EQ(stp->get_blockchain_based_list()->block_height(), block_list.size() - 1); + + MDEBUG("stp->get_blockchain_based_list()->block_height(): " << stp->get_blockchain_based_list()->block_height()); + + CHECK_EQ(stp->get_blockchain_based_list()->tiers().size(), 4); + uint64_t stake_amount = 0; + for (const auto & tier : stp->get_blockchain_based_list()->tiers()) { + for (const auto & sn: tier) { + MDEBUG("sn: " << sn.supernode_public_id << ", stake_amount: " << sn.amount << ", expired at height: " << sn.unlock_time); + stake_amount += sn.amount; + } + } + CHECK_EQ(stake_amount, MK_COINS(50000) * g_supernode_list.size()); + + MDEBUG("stake_tx_storage->get_tx_count: " << stp->get_storage()->get_tx_count()); + MDEBUG("stake_tx_storage->get_last_processed_block_index: " << stp->get_storage()->get_last_processed_block_index()); + return true; +} + +bool gen_rta_test::check_stake_expired(core &c, size_t ev_index, const std::vector &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_rta_test::check_stake_expired"); + cryptonote::account_base miner0 = boost::get(events[1]); + cryptonote::account_base alice0 = boost::get(events[2]); + std::vector chain; + map_hash2tx_t mtx; + std::vector block_list; + + // request count doesn't have to be exact value + bool r = c.get_blocks(0, 5 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + + find_block_chain(events, chain, mtx, get_block_hash(block_list.back())); + + StakeTransactionProcessor * stp = c.get_stake_tx_processor(); + + CHECK_EQ(stp->get_blockchain_based_list()->block_height(), block_list.size() - 1); + MDEBUG("stp->get_blockchain_based_list()->block_height(): " << stp->get_blockchain_based_list()->block_height()); + CHECK_EQ(stp->get_blockchain_based_list()->tiers().size(), 4); + uint64_t stake_amount = 0; + for (const auto & tier : stp->get_blockchain_based_list()->tiers()) { + for (const auto & sn: tier) { + MDEBUG("sn: " << sn.supernode_public_id << ", stake_amount: " << sn.amount << ", expired at height: " << sn.unlock_time); + stake_amount += sn.amount; + } + } + CHECK_EQ(stake_amount, 0); + MDEBUG("stake_tx_storage->get_tx_count: " << stp->get_storage()->get_tx_count()); + MDEBUG("stake_tx_storage->get_last_processed_block_index: " << stp->get_storage()->get_last_processed_block_index()); + + return true; +} diff --git a/tests/core_tests/rta_tests.h b/tests/core_tests/rta_tests.h new file mode 100644 index 000000000..e88ebb880 --- /dev/null +++ b/tests/core_tests/rta_tests.h @@ -0,0 +1,121 @@ +// Copyright (c) 2019, Graft Project +// 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 "chaingen.h" +#include "misc_log_ex.h" +#include + +struct Supernode { + struct Keypair { + crypto::public_key pkey; + crypto::secret_key skey; + }; + cryptonote::account_base account; + Keypair keys; + Supernode() + { + account.generate(); + crypto::generate_keys(keys.pkey, keys.skey); + } + Supernode(int idx); + crypto::signature signature() + { + std::string address = cryptonote::get_account_address_as_str( + cryptonote::MAINNET, false, account.get_keys().m_account_address); + std::string msg = address + ":" + epee::string_tools::pod_to_hex(keys.pkey); + crypto::signature result; + sign_message(msg, result); + return result; + } + void sign_message(const std::string& msg, crypto::signature &signature) + { + crypto::hash hash; + crypto::cn_fast_hash(msg.data(), msg.size(), hash); + crypto::generate_signature(hash, keys.pkey, keys.skey, signature); + } +}; + +/////////////////////////////////////////// +/// gen_rta_disqualification_test +/// + +struct gen_rta_disqualification_test : public test_chain_unit_base +{ + using single_callback_t = std::function& events)>; + + gen_rta_disqualification_test(); + + // test generator method: here we define the test sequence + bool generate(std::vector& events) const; + +private: + void set_single_callback(std::vector& events, const single_callback_t& func) const; + bool call_single_callback(cryptonote::core& c, size_t ev_index, const std::vector& events); + + cryptonote::transaction make_disqualification1_transaction(std::vector& events, std::vector>& tiers, int disq_sn_idx) const; + cryptonote::transaction make_disqualification2_transaction(std::vector& events, std::vector>& tiers, std::vector disq_sn_idxs) const; + + bool check_bbl_cnt(cryptonote::core& c, int expected_cnt, uint64_t depth, const std::string& context) const; + void check_bbl_cnt(const std::string& context, std::vector& events, int expected_cnt, uint64_t depth = 0) const; + + mutable int tmp_payment_idx = 0; +}; + +/////////////////////////////////////////// +/// gen_rta_test +/// + +struct gen_rta_test : public test_chain_unit_base +{ + gen_rta_test(); + + // test generator method: here we define the test sequence + bool generate(std::vector& events) const; + + // bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& blk); + + bool check1(cryptonote::core& c, size_t ev_index, const std::vector& events); + bool check_stake_registered(cryptonote::core& c, size_t ev_index, const std::vector& events); + bool check_stake_expired(cryptonote::core& c, size_t ev_index, const std::vector& events); +private: +}; + +// this is how to define hardforks table for the cryptonote::core +//template<> +//struct get_test_options { +// // first element is hf number, second is the height for this hf; last element in array should be {0,0} +// const std::pair hard_forks[2] = {std::make_pair(1, 0), std::make_pair(0, 0)}; +// //const std::pair hard_forks[3] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(0, 0)}; +// const cryptonote::test_options test_options = { +// hard_forks +// }; +//};