// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include #include #include #include #include #include #include #include #include #include #include "common/string_util.h" #include "common/hex.h" #include "common/varint.h" #include "common/median.h" #include "epee/console_handler.h" #include "common/rules.h" #include "cryptonote_config.h" #include "p2p/net_node.h" #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/miner.h" #include "cryptonote_core/uptime_proof.h" #include "oxen_economy.h" #include "ringct/rctSigs.h" #include "chaingen.h" #include "device/device.hpp" #include "crypto/crypto.h" extern "C" { #include } #include void oxen_register_callback(std::vector &events, std::string const &callback_name, oxen_callback callback) { events.push_back(oxen_callback_entry{callback_name, callback}); } std::vector oxen_generate_hard_fork_table(uint8_t hf_version, uint64_t pos_delay) { assert(hf_version < cryptonote::network_version_count); // We always need block 0 == v7 for the genesis block: std::vector result{{cryptonote::network_version_7, 0, 0}}; uint64_t version_height = 1; // HF15 reduces and HF16+ eliminates miner block rewards, so we need to ensure we have enough // HF14 blocks to generate enough LOKI for tests: if (hf_version > cryptonote::network_version_14_blink) { result.push_back({cryptonote::network_version_14_blink, 0, version_height}); version_height += pos_delay; } result.push_back({hf_version, 0, version_height}); return result; } uint64_t oxen_chain_generator_db::get_block_height(crypto::hash const &hash) const { oxen_blockchain_entry const &entry = this->block_table.at(hash); uint64_t result = cryptonote::get_block_height(entry.block); return result; } cryptonote::block oxen_chain_generator_db::get_block_from_height(uint64_t height) const { assert(height < blocks.size()); cryptonote::block const &result = this->blocks[height].block; assert(cryptonote::get_block_height(result) == height); return result; } cryptonote::block_header oxen_chain_generator_db::get_block_header_from_height(uint64_t height) const { return get_block_from_height(height); } service_nodes::service_node_keys oxen_chain_generator::get_cached_keys(const crypto::public_key &pubkey) const { service_nodes::service_node_keys keys; keys.pub = pubkey; auto it = service_node_keys_.find(keys.pub); assert(it != service_node_keys_.end()); if (it != service_node_keys_.end()) keys.key = it->second; return keys; } bool oxen_chain_generator_db::get_tx(const crypto::hash &h, cryptonote::transaction &tx) const { auto it = tx_table.find(h); if (it == tx_table.end()) return false; tx = it->second; return true; } std::vector oxen_chain_generator_db::get_checkpoints_range(uint64_t start, uint64_t end, size_t num_desired_checkpoints) const { assert(start < blocks.size()); assert(end < blocks.size()); std::vector result = {}; int offset = 1; if (start >= end) offset = -1; for (int index = static_cast(start); index != static_cast(end); index += offset) { if (result.size() >= num_desired_checkpoints) break; if (blocks[index].checkpointed) result.push_back(blocks[index].checkpoint); } if (result.size() < num_desired_checkpoints && blocks[end].checkpointed) result.push_back(blocks[end].checkpoint); return result; } std::vector oxen_chain_generator_db::get_blocks_range(const uint64_t &h1, const uint64_t &h2) const { assert(h1 <= h2); std::vector result; result.reserve(h2 - h1); for (uint64_t height = h1; height <= h2; height++) { result.push_back(blocks[height].block); assert(cryptonote::get_block_height(result.back()) == height); } return result; } oxen_chain_generator::oxen_chain_generator(std::vector& events, const std::vector& hard_forks, std::string first_miner_seed) : events_(events) , hard_forks_(hard_forks) , sqlite_db_(std::make_unique(cryptonote::FAKECHAIN, ":memory:")) { bool init = ons_db_->init(nullptr, cryptonote::FAKECHAIN, ons::init_oxen_name_system("", false /*read_only*/)); assert(init); if (first_miner_seed == "") { first_miner_.generate(); } else { crypto::secret_key seeded_secret_key; tools::hex_to_type(first_miner_seed, seeded_secret_key); first_miner_.generate(seeded_secret_key, true); } oxen_blockchain_entry genesis = oxen_chain_generator::create_genesis_block(first_miner_, 1338224400); events_.push_back(genesis.block); db_.blocks.push_back(genesis); // NOTE: Load hard forks into the event vector which gets extracted out at // run-time. This is preferred over-overriding a get_test_options<> struct // since this forces the hard fork information to be specified inline at the // testing site, so modifying and updating tests is localised to one spot. event_replay_settings settings = {}; settings.hard_forks = hard_forks; events_.push_back(settings); } service_nodes::quorum_manager oxen_chain_generator::top_quorum() const { service_nodes::quorum_manager result = top().service_node_state.quorums; return result; } service_nodes::quorum_manager oxen_chain_generator::quorum(uint64_t height) const { assert(height > 0 && height < db_.blocks.size()); service_nodes::quorum_manager result = db_.blocks[height].service_node_state.quorums; return result; } std::shared_ptr oxen_chain_generator::get_quorum(service_nodes::quorum_type type, uint64_t height) const { // TODO(oxen): Bad copy pasta from get_quorum, if it ever changes at the source this will break :< if (type == service_nodes::quorum_type::checkpointing) { assert(height >= service_nodes::REORG_SAFETY_BUFFER_BLOCKS_POST_HF12); height -= service_nodes::REORG_SAFETY_BUFFER_BLOCKS_POST_HF12; } assert(height > 0 && height < db_.blocks.size()); service_nodes::quorum_manager manager = db_.blocks[height].service_node_state.quorums; std::shared_ptr result = manager.get(type); return result; } oxen_blockchain_entry &oxen_chain_generator::add_block(oxen_blockchain_entry const &entry, bool can_be_added_to_blockchain, std::string const &fail_msg) { crypto::hash block_hash = get_block_hash(entry.block); if (can_be_added_to_blockchain) { db_.blocks.push_back(entry); assert(db_.block_table.count(block_hash) == 0); db_.block_table[block_hash] = db_.blocks.back(); } else { assert(db_.block_table.count(block_hash) == 0); db_.block_table[block_hash] = entry; } oxen_blockchain_entry &result = (can_be_added_to_blockchain) ? db_.blocks.back() : db_.block_table[block_hash]; for (cryptonote::transaction &tx : result.txs) { crypto::hash tx_hash = get_transaction_hash(tx); assert(db_.tx_table.count(tx_hash) == 0); db_.tx_table[tx_hash] = tx; } if (can_be_added_to_blockchain && entry.block.major_version >= cryptonote::network_version_15_ons) { ons_db_->add_block(entry.block, entry.txs); } sqlite_db_->add_block(entry.block, entry.service_node_state); // TODO(oxen): State history culling and alt states state_history_.emplace_hint(state_history_.end(), result.service_node_state); if (result.checkpointed) { oxen_block_with_checkpoint data = {}; data.has_checkpoint = true; data.block = result.block; data.checkpoint = result.checkpoint; events_.push_back(oxen_blockchain_addable(data, can_be_added_to_blockchain, fail_msg)); } else { events_.push_back(oxen_blockchain_addable(result.block, can_be_added_to_blockchain, fail_msg)); } cryptonote::block sopthing = entry.block; return result; } cryptonote::account_base oxen_chain_generator::add_account() { cryptonote::account_base account; account.generate(); events_.push_back(account); return account; } void oxen_chain_generator::add_blocks_until_version(uint8_t hf_version) { assert(hard_forks_.size()); assert(hf_version_ <= hard_forks_.back().version); assert(db_.blocks.size() >= 1); // NOTE: We must have genesis block for (;;) { oxen_blockchain_entry &entry = create_and_add_next_block(); if (entry.block.major_version == hf_version) return; } } void oxen_chain_generator::add_n_blocks(int n) { for (auto i = 0; i < n; ++i) { create_and_add_next_block(); } } bool oxen_chain_generator::add_blocks_until_next_checkpointable_height() { if (top().service_node_state.active_service_nodes_infos().size() < service_nodes::CHECKPOINT_QUORUM_SIZE) return false; // NOTE: Add blocks until we get to the first height that has a checkpointing // quorum AND there are service nodes in the quorum. Note we do this naively // as tests shouldn't have to care about implementation details. for (;;) { create_and_add_next_block(); std::shared_ptr quorum = get_quorum(service_nodes::quorum_type::checkpointing, height()); if (quorum && quorum->validators.size()) break; } return true; } void oxen_chain_generator::add_service_node_checkpoint(uint64_t block_height, size_t num_votes) { oxen_blockchain_entry &entry = db_.blocks[block_height]; entry.checkpointed = true; entry.checkpoint = create_service_node_checkpoint(block_height, num_votes); events_.push_back(entry.checkpoint); } void oxen_chain_generator::add_mined_money_unlock_blocks() { add_n_blocks(CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); } void oxen_chain_generator::add_transfer_unlock_blocks() { add_n_blocks(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE); } void oxen_chain_generator::add_tx(cryptonote::transaction const &tx, bool can_be_added_to_blockchain, std::string const &fail_msg, bool kept_by_block) { events_.emplace_back(oxen_blockchain_addable{{tx, kept_by_block}, can_be_added_to_blockchain, fail_msg}); } cryptonote::transaction oxen_chain_generator::create_and_add_oxen_name_system_tx(cryptonote::account_base const &src, uint8_t hf_version, ons::mapping_type type, std::string const &name, ons::mapping_value const &value, ons::generic_owner const *owner, ons::generic_owner const *backup_owner, bool kept_by_block) { cryptonote::transaction t = create_oxen_name_system_tx(src, hf_version, type, name, value, owner, backup_owner); add_tx(t, true /*can_be_added_to_blockchain*/, ""/*fail_msg*/, kept_by_block); return t; } cryptonote::transaction oxen_chain_generator::create_and_add_oxen_name_system_tx_update(cryptonote::account_base const &src, uint8_t hf_version, ons::mapping_type type, std::string const &name, ons::mapping_value const *value, ons::generic_owner const *owner, ons::generic_owner const *backup_owner, ons::generic_signature *signature, bool kept_by_block) { cryptonote::transaction t = create_oxen_name_system_tx_update(src, hf_version, type, name, value, owner, backup_owner, signature); add_tx(t, true /*can_be_added_to_blockchain*/, ""/*fail_msg*/, kept_by_block); return t; } cryptonote::transaction oxen_chain_generator::create_and_add_oxen_name_system_tx_renew(cryptonote::account_base const &src, uint8_t hf_version, ons::mapping_type type, std::string const &name, bool kept_by_block) { cryptonote::transaction t = create_oxen_name_system_tx_renew(src, hf_version, type, name); add_tx(t, true /*can_be_added_to_blockchain*/, ""/*fail_msg*/, kept_by_block); return t; } cryptonote::transaction oxen_chain_generator::create_and_add_tx(const cryptonote::account_base &src, const cryptonote::account_public_address &dest, uint64_t amount, uint64_t fee, bool kept_by_block) { cryptonote::transaction t = create_tx(src, dest, amount, fee); oxen_tx_builder(events_, t, db_.blocks.back().block, src, dest, amount, hf_version_).with_fee(fee).build(); add_tx(t, true /*can_be_added_to_blockchain*/, ""/*fail_msg*/, kept_by_block); return t; } cryptonote::transaction oxen_chain_generator::create_and_add_big_tx( const cryptonote::account_base &src, const cryptonote::account_public_address &dest, uint64_t junk_size, uint64_t amount, uint64_t fee, bool kept_by_block) { cryptonote::transaction t = create_tx(src, dest, amount, fee); oxen_tx_builder(events_, t, db_.blocks.back().block, src, dest, amount, hf_version_).with_fee(fee).with_junk(junk_size).build(); add_tx(t, true /*can_be_added_to_blockchain*/, ""/*fail_msg*/, kept_by_block); return t; } cryptonote::transaction oxen_chain_generator::create_and_add_state_change_tx(service_nodes::new_state state, const crypto::public_key &pub_key, uint16_t reasons_all, uint16_t reasons_any, uint64_t height, const std::vector &voters, uint64_t fee, bool kept_by_block) { cryptonote::transaction result = create_state_change_tx(state, pub_key, reasons_all, reasons_any, height, voters, fee); add_tx(result, true /*can_be_added_to_blockchain*/, "" /*fail_msg*/, kept_by_block); return result; } cryptonote::transaction oxen_chain_generator::create_and_add_registration_tx(const cryptonote::account_base &src, const cryptonote::keypair &sn_keys, bool kept_by_block) { cryptonote::transaction result = create_registration_tx(src, sn_keys); add_tx(result, true /*can_be_added_to_blockchain*/, "" /*fail_msg*/, kept_by_block); return result; } cryptonote::transaction oxen_chain_generator::create_and_add_staking_tx(const crypto::public_key &pub_key, const cryptonote::account_base &src, uint64_t amount, bool kept_by_block) { cryptonote::transaction result = create_staking_tx(pub_key, src, amount); add_tx(result, true /*can_be_added_to_blockchain*/, "" /*fail_msg*/, kept_by_block); return result; } oxen_blockchain_entry &oxen_chain_generator::create_and_add_next_block(const std::vector& txs, cryptonote::checkpoint_t const *checkpoint, bool can_be_added_to_blockchain, std::string const &fail_msg) { oxen_blockchain_entry entry = create_next_block(txs, checkpoint); oxen_blockchain_entry &result = add_block(entry, can_be_added_to_blockchain, fail_msg); return result; } cryptonote::transaction oxen_chain_generator::create_tx(const cryptonote::account_base &src, const cryptonote::account_public_address &dest, uint64_t amount, uint64_t fee) const { cryptonote::transaction t; oxen_tx_builder(events_, t, db_.blocks.back().block, src, dest, amount, hf_version_).with_fee(fee).build(); return t; } cryptonote::transaction oxen_chain_generator::create_registration_tx(const cryptonote::account_base &src, const cryptonote::keypair &service_node_keys, uint64_t src_portions, uint64_t src_operator_cut, std::array const &contributions, int num_contributors) const { cryptonote::transaction result = {}; { std::vector contributors; std::vector portions; contributors.reserve(1 + num_contributors); portions.reserve (1 + num_contributors); contributors.push_back(src.get_keys().m_account_address); portions.push_back(src_portions); for (int i = 0; i < num_contributors; i++) { oxen_service_node_contribution const &entry = contributions[i]; contributors.push_back(entry.contributor); portions.push_back (entry.portions); } uint64_t new_height = get_block_height(top().block) + 1; uint8_t new_hf_version = get_hf_version_at(new_height); const auto staking_requirement = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, new_height); uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement); uint64_t unlock_time = 0; if (new_hf_version < cryptonote::network_version_11_infinite_staking) unlock_time = new_height + service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN); std::vector extra; cryptonote::add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub); const uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW; crypto::hash hash; if (!cryptonote::get_registration_hash(contributors, src_operator_cut, portions, exp_timestamp, hash)) { MERROR("Could not make registration hash from addresses and portions"); return {}; } crypto::signature signature; crypto::generate_signature(hash, service_node_keys.pub, service_node_keys.sec, signature); add_service_node_register_to_tx_extra(extra, contributors, src_operator_cut, portions, exp_timestamp, signature); add_service_node_contributor_to_tx_extra(extra, contributors.at(0)); oxen_tx_builder(events_, result, top().block, src /*from*/, src.get_keys().m_account_address /*to*/, amount, new_hf_version) .with_tx_type(cryptonote::txtype::stake) .with_unlock_time(unlock_time) .with_extra(extra) .build(); } service_node_keys_[service_node_keys.pub] = service_node_keys.sec; // NOTE: Save generated key for reuse later if we need to interact with the node again return result; } cryptonote::transaction oxen_chain_generator::create_staking_tx(const crypto::public_key &pub_key, const cryptonote::account_base &src, uint64_t amount) const { cryptonote::transaction result = {}; std::vector extra; cryptonote::add_service_node_pubkey_to_tx_extra(extra, pub_key); cryptonote::add_service_node_contributor_to_tx_extra(extra, src.get_keys().m_account_address); uint64_t new_height = get_block_height(top().block) + 1; uint8_t new_hf_version = get_hf_version_at(new_height); uint64_t unlock_time = 0; if (new_hf_version < cryptonote::network_version_11_infinite_staking) unlock_time = new_height + service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN); oxen_tx_builder(events_, result, top().block, src /*from*/, src.get_keys().m_account_address /*to*/, amount, new_hf_version) .with_tx_type(cryptonote::txtype::stake) .with_unlock_time(unlock_time) .with_extra(extra) .build(); return result; } cryptonote::transaction oxen_chain_generator::create_state_change_tx(service_nodes::new_state state, const crypto::public_key &pub_key, uint16_t reasons_all, uint16_t reasons_any, uint64_t height, const std::vector& voters, uint64_t fee) const { if (height == UINT64_MAX) height = this->height(); auto hf_version = get_hf_version_at(height + 1); service_nodes::quorum_manager const &quorums = quorum(height); std::vector const &validator_service_nodes = quorums.obligations->validators; std::vector const &worker_service_nodes = quorums.obligations->workers; size_t worker_index = std::numeric_limits::max(); for (size_t i = 0; i < worker_service_nodes.size(); i++) { crypto::public_key const &check_key = worker_service_nodes[i]; if (pub_key == check_key) worker_index = i; } assert(worker_index < worker_service_nodes.size()); using scver = cryptonote::tx_extra_service_node_state_change::version_t; cryptonote::tx_extra_service_node_state_change state_change_extra( hf_version >= cryptonote::network_version_18 ? scver::v4_reasons : scver::v0, state, height, worker_index, reasons_all, reasons_any, {}); if (voters.size()) { for (const auto voter_index : voters) { auto voter_keys = get_cached_keys(validator_service_nodes[voter_index]); service_nodes::quorum_vote_t vote = service_nodes::make_state_change_vote(state_change_extra.block_height, voter_index, state_change_extra.service_node_index, state, 0, voter_keys); state_change_extra.votes.push_back({vote.signature, (uint32_t)voter_index}); } } else { for (size_t i = 0; i < service_nodes::STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE; i++) { auto voter_keys = get_cached_keys(validator_service_nodes[i]); service_nodes::quorum_vote_t vote = service_nodes::make_state_change_vote(state_change_extra.block_height, i, state_change_extra.service_node_index, state, 0, voter_keys); state_change_extra.votes.push_back({vote.signature, (uint32_t)i}); } } cryptonote::transaction result; { std::vector extra; const bool full_tx_made = cryptonote::add_service_node_state_change_to_tx_extra(result.extra, state_change_extra, hf_version); assert(full_tx_made); if (fee) oxen_tx_builder(events_, result, top().block, first_miner_, first_miner_.get_keys().m_account_address, 0 /*amount*/, hf_version) .with_tx_type(cryptonote::txtype::state_change) .with_fee(fee) .with_extra(extra) .build(); else { result.type = cryptonote::txtype::state_change; result.version = cryptonote::transaction::get_max_version_for_hf(hf_version); } } return result; } cryptonote::checkpoint_t oxen_chain_generator::create_service_node_checkpoint(uint64_t block_height, size_t num_votes) const { service_nodes::quorum const &quorum = *get_quorum(service_nodes::quorum_type::checkpointing, block_height); assert(num_votes < quorum.validators.size()); oxen_blockchain_entry const &entry = db_.blocks[block_height]; crypto::hash const block_hash = cryptonote::get_block_hash(entry.block); cryptonote::checkpoint_t result = service_nodes::make_empty_service_node_checkpoint(block_hash, block_height); result.signatures.reserve(num_votes); for (size_t i = 0; i < num_votes; i++) { auto keys = get_cached_keys(quorum.validators[i]); service_nodes::quorum_vote_t vote = service_nodes::make_checkpointing_vote(entry.block.major_version, result.block_hash, block_height, i, keys); result.signatures.push_back(service_nodes::quorum_signature(vote.index_in_group, vote.signature)); } return result; } cryptonote::transaction oxen_chain_generator::create_oxen_name_system_tx(cryptonote::account_base const &src, uint8_t hf_version, ons::mapping_type type, std::string const &name, ons::mapping_value const &value, ons::generic_owner const *owner, ons::generic_owner const *backup_owner, std::optional burn_override) const { ons::generic_owner generic_owner = {}; if (owner) { generic_owner = *owner; } else { generic_owner = ons::make_monero_owner(src.get_keys().m_account_address, false /*subaddress*/); } cryptonote::block const &head = top().block; uint64_t new_height = get_block_height(top().block) + 1; uint8_t new_hf_version = get_hf_version_at(new_height); uint64_t burn = burn_override.value_or(ons::burn_needed(new_hf_version, type)); auto lcname = tools::lowercase_ascii_string(name); crypto::hash name_hash = ons::name_to_hash(lcname); std::string name_base64_hash = ons::name_to_base64_hash(lcname); crypto::hash prev_txid = crypto::null_hash; if (ons::mapping_record mapping = ons_db_->get_mapping(type, name_base64_hash, new_height)) prev_txid = mapping.txid; ons::mapping_value encrypted_value = value; bool encrypted = encrypted_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_15_ons); assert(encrypted); std::vector extra; cryptonote::tx_extra_oxen_name_system data = cryptonote::tx_extra_oxen_name_system::make_buy(generic_owner, backup_owner, type, name_hash, encrypted_value.to_string(), prev_txid); cryptonote::add_oxen_name_system_to_tx_extra(extra, data); cryptonote::add_burned_amount_to_tx_extra(extra, burn); cryptonote::transaction result = {}; oxen_tx_builder(events_, result, head, src /*from*/, src.get_keys().m_account_address, 0 /*amount*/, new_hf_version) .with_tx_type(cryptonote::txtype::oxen_name_system) .with_extra(extra) .with_fee(burn + TESTS_DEFAULT_FEE) .build(); return result; } cryptonote::transaction oxen_chain_generator::create_oxen_name_system_tx_update(cryptonote::account_base const &src, uint8_t hf_version, ons::mapping_type type, std::string const &name, ons::mapping_value const *value, ons::generic_owner const *owner, ons::generic_owner const *backup_owner, ons::generic_signature *signature, bool use_asserts) const { auto lcname = tools::lowercase_ascii_string(name); crypto::hash name_hash = ons::name_to_hash(lcname); crypto::hash prev_txid = {}; { std::string name_base64_hash = ons::name_to_base64_hash(lcname); ons::mapping_record mapping = ons_db_->get_mapping(type, name_base64_hash); if (use_asserts) assert(mapping); prev_txid = mapping.txid; } ons::mapping_value encrypted_value = {}; if (value) { encrypted_value = *value; if (!encrypted_value.encrypted) { assert(!signature); // Can't specify a signature with an unencrypted value because encrypting generates a new nonce and would invalidate it bool encrypted = encrypted_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_15_ons); if (use_asserts) assert(encrypted); } } ons::generic_signature signature_ = {}; if (!signature) { signature = &signature_; auto data = ons::tx_extra_signature(encrypted_value.to_view(), owner, backup_owner, prev_txid); crypto::hash hash{}; if (!data.empty()) crypto_generichash(reinterpret_cast(hash.data), sizeof(hash), reinterpret_cast(data.data()), data.size(), nullptr, 0); generate_signature(hash, src.get_keys().m_account_address.m_spend_public_key, src.get_keys().m_spend_secret_key, signature->monero); signature->type = ons::generic_owner_sig_type::monero; } std::vector extra; cryptonote::tx_extra_oxen_name_system data = cryptonote::tx_extra_oxen_name_system::make_update(*signature, type, name_hash, encrypted_value.to_view(), owner, backup_owner, prev_txid); cryptonote::add_oxen_name_system_to_tx_extra(extra, data); cryptonote::block const &head = top().block; uint64_t new_height = get_block_height(top().block) + 1; uint8_t new_hf_version = get_hf_version_at(new_height); cryptonote::transaction result = {}; oxen_tx_builder(events_, result, head, src /*from*/, src.get_keys().m_account_address, 0 /*amount*/, new_hf_version) .with_tx_type(cryptonote::txtype::oxen_name_system) .with_extra(extra) .with_fee(TESTS_DEFAULT_FEE) .build(); return result; } cryptonote::transaction oxen_chain_generator::create_oxen_name_system_tx_update_w_extra(cryptonote::account_base const &src, uint8_t hf_version, cryptonote::tx_extra_oxen_name_system const &ons_extra) const { std::vector extra; cryptonote::add_oxen_name_system_to_tx_extra(extra, ons_extra); cryptonote::block const &head = top().block; uint64_t new_height = get_block_height(top().block) + 1; uint8_t new_hf_version = get_hf_version_at(new_height); cryptonote::transaction result = {}; oxen_tx_builder(events_, result, head, src /*from*/, src.get_keys().m_account_address, 0 /*amount*/, new_hf_version) .with_tx_type(cryptonote::txtype::oxen_name_system) .with_extra(extra) .with_fee(TESTS_DEFAULT_FEE) .build(); return result; } cryptonote::transaction oxen_chain_generator::create_oxen_name_system_tx_renew(cryptonote::account_base const &src, uint8_t hf_version, ons::mapping_type type, std::string const &name, std::optional burn_override) const { auto lcname = tools::lowercase_ascii_string(name); crypto::hash name_hash = ons::name_to_hash(lcname); crypto::hash prev_txid = {}; { std::string name_base64_hash = ons::name_to_base64_hash(lcname); ons::mapping_record mapping = ons_db_->get_mapping(type, name_base64_hash); prev_txid = mapping.txid; } uint8_t new_hf_version = get_hf_version_at(get_block_height(top().block) + 1); uint64_t burn = burn_override.value_or(ons::burn_needed(new_hf_version, type)); std::vector extra; cryptonote::tx_extra_oxen_name_system data = cryptonote::tx_extra_oxen_name_system::make_renew(type, name_hash, prev_txid); cryptonote::add_oxen_name_system_to_tx_extra(extra, data); cryptonote::add_burned_amount_to_tx_extra(extra, burn); cryptonote::block const &head = top().block; cryptonote::transaction result = {}; oxen_tx_builder(events_, result, head, src /*from*/, src.get_keys().m_account_address, 0 /*amount*/, new_hf_version) .with_tx_type(cryptonote::txtype::oxen_name_system) .with_extra(extra) .with_fee(burn + TESTS_DEFAULT_FEE) .build(); return result; } static void fill_nonce_with_test_generator(test_generator *generator, cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height) { cryptonote::randomx_longhash_context randomx_context = {}; if (generator->m_hf_version >= cryptonote::network_version_12_checkpointing) { randomx_context.seed_height = crypto::rx_seedheight(height); cryptonote::block prev = blk; do { prev = generator->m_blocks_info[prev.prev_id].block; } while (cryptonote::get_block_height(prev) != randomx_context.seed_height); randomx_context.seed_block_hash = cryptonote::get_block_hash(prev); randomx_context.current_blockchain_height = height; } blk.nonce = 0; auto get_block_hash = [&randomx_context](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash) { hash = cryptonote::get_block_longhash(cryptonote::FAKECHAIN, randomx_context, b, height, threads); return true; }; while (!cryptonote::miner::find_nonce_for_given_block(get_block_hash, blk, diffic, height)) blk.timestamp++; } void fill_nonce_with_oxen_generator(oxen_chain_generator const *generator, cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height) { cryptonote::randomx_longhash_context randomx_context = {}; if (generator->blocks().size() && generator->hardfork() >= cryptonote::network_version_12_checkpointing) { randomx_context.seed_height = crypto::rx_seedheight(height); randomx_context.seed_block_hash = cryptonote::get_block_hash(generator->blocks()[randomx_context.seed_height].block); randomx_context.current_blockchain_height = height; } blk.nonce = 0; auto get_block_hash = [&randomx_context](const cryptonote::block &blk, uint64_t height, unsigned int threads, crypto::hash &hash) { hash = cryptonote::get_block_longhash(cryptonote::FAKECHAIN, randomx_context, blk, height, threads); return true; }; while (!cryptonote::miner::find_nonce_for_given_block(get_block_hash, blk, TEST_DEFAULT_DIFFICULTY, height)) blk.timestamp++; } oxen_blockchain_entry oxen_chain_generator::create_genesis_block(const cryptonote::account_base &miner, uint64_t timestamp) { uint64_t height = 0; oxen_blockchain_entry result = {}; cryptonote::block &blk = result.block; blk.major_version = hf_version_; blk.minor_version = hf_version_; blk.timestamp = timestamp; blk.prev_id = crypto::null_hash; // TODO(doyle): Does this evaluate to 0? If so we can simplify this a lot more size_t target_block_weight = get_transaction_weight(blk.miner_tx); std::optional> sn_rwds; uint64_t block_rewards = 0; while (true) { bool constructed = false; std::tie(constructed, block_rewards) = construct_miner_tx(height, 0 /*median_weight*/, 0 /*already_generated_coins*/, target_block_weight, 0 /*total_fee*/, blk.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner.get_keys().m_account_address), sn_rwds, cryptonote::blobdata(), hf_version_); assert(constructed); size_t actual_block_weight = get_transaction_weight(blk.miner_tx); if (target_block_weight < actual_block_weight) { target_block_weight = actual_block_weight; } else if (actual_block_weight < target_block_weight) { size_t delta = target_block_weight - actual_block_weight; blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); actual_block_weight = get_transaction_weight(blk.miner_tx); if (actual_block_weight == target_block_weight) { break; } else { assert(target_block_weight < actual_block_weight); delta = actual_block_weight - target_block_weight; blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta); actual_block_weight = get_transaction_weight(blk.miner_tx); if (actual_block_weight == target_block_weight) { break; } else { assert(actual_block_weight < target_block_weight); blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); target_block_weight = get_transaction_weight(blk.miner_tx); } } } else { break; } } fill_nonce_with_oxen_generator(this, blk, TEST_DEFAULT_DIFFICULTY, height); result.block_weight = get_transaction_weight(blk.miner_tx); uint64_t block_reward, block_reward_unpenalized; cryptonote::get_base_block_reward(0 /*median_weight*/, result.block_weight, 0 /*already_generated_coins*/, block_reward, block_reward_unpenalized, hf_version_, height); result.already_generated_coins = block_reward; return result; } bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create_block_params ¶ms, const std::vector &tx_list) const { assert(params.hf_version >= params.prev.block.major_version); uint64_t height = get_block_height(params.prev.block) + 1; entry = {}; cryptonote::block &blk = entry.block; blk.major_version = params.hf_version; blk.minor_version = params.hf_version; blk.height = height; blk.timestamp = params.timestamp; blk.prev_id = get_block_hash(params.prev.block); uint64_t total_fee = params.total_fee; bool calc_total_fee = total_fee == 0; size_t txs_weight = 0; blk.tx_hashes.reserve(tx_list.size()); for(const cryptonote::transaction &tx : tx_list) { blk.tx_hashes.push_back(get_transaction_hash(tx)); uint64_t fee = 0; bool r = get_tx_miner_fee(tx, fee, blk.major_version >= HF_VERSION_FEE_BURNING); CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); txs_weight += get_transaction_weight(tx); if (calc_total_fee) total_fee += fee; } // NOTE: Calculate governance cryptonote::oxen_miner_tx_context miner_tx_context; service_nodes::quorum pulse_quorum; std::vector active_snode_list = params.prev.service_node_state.active_service_nodes_infos(); bool pulse_block_is_possible = blk.major_version >= cryptonote::network_version_16_pulse && active_snode_list.size() >= service_nodes::pulse_min_service_nodes(cryptonote::FAKECHAIN); bool make_pulse_block = (params.type == oxen_create_block_type::automatic && pulse_block_is_possible) || params.type == oxen_create_block_type::pulse; if (make_pulse_block) { // NOTE: Set up Pulse Header blk.pulse.validator_bitset = service_nodes::pulse_validator_bit_mask(); // NOTE: Everyone participates blk.pulse.round = params.pulse_round; for (size_t i = 0; i < sizeof(blk.pulse.random_value.data); i++) blk.pulse.random_value.data[i] = static_cast(tools::uniform_distribution_portable(tools::rng, 256)); // NOTE: Get Pulse Quorum necessary for this block std::vector entropy = service_nodes::get_pulse_entropy_for_next_block(db_, params.prev.block, blk.pulse.round); pulse_quorum = service_nodes::generate_pulse_quorum(cryptonote::FAKECHAIN, params.block_leader.key, blk.major_version, active_snode_list, entropy, blk.pulse.round); assert(pulse_quorum.validators.size() == service_nodes::PULSE_QUORUM_NUM_VALIDATORS); assert(pulse_quorum.workers.size() == 1); service_nodes::payout block_producer = {}; if (pulse_quorum.workers[0] == params.block_leader.key) { block_producer = params.block_leader; } else { crypto::public_key block_producer_key = pulse_quorum.workers[0]; auto it = params.prev.service_node_state.service_nodes_infos.find(block_producer_key); assert(it != params.prev.service_node_state.service_nodes_infos.end()); block_producer = service_nodes::service_node_info_to_payout(block_producer_key, *(it->second)); } miner_tx_context = cryptonote::oxen_miner_tx_context::pulse_block(cryptonote::FAKECHAIN, block_producer, params.block_leader); blk.service_node_winner_key = miner_tx_context.pulse_block_producer.key; } else { miner_tx_context = cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, params.miner_acc.get_keys().m_account_address, params.block_leader); blk.service_node_winner_key = miner_tx_context.block_leader.key; } if (blk.major_version >= cryptonote::network_version_10_bulletproofs && cryptonote::height_has_governance_output(cryptonote::FAKECHAIN, blk.major_version, height)) { constexpr uint64_t num_blocks = cryptonote::get_config(cryptonote::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; uint64_t start_height = height - num_blocks; if (blk.major_version == cryptonote::network_version_15_ons) miner_tx_context.batched_governance = FOUNDATION_REWARD_HF15 * num_blocks; else if (blk.major_version == cryptonote::network_version_16_pulse) miner_tx_context.batched_governance = (FOUNDATION_REWARD_HF15 + CHAINFLIP_LIQUIDITY_HF16) * num_blocks; else if (blk.major_version >= cryptonote::network_version_17) miner_tx_context.batched_governance = FOUNDATION_REWARD_HF17 * num_blocks; else { for (int i = (int)get_block_height(params.prev.block), count = 0; i >= 0 && count <= (int)num_blocks; i--, count++) { oxen_blockchain_entry const &historical_entry = db_.blocks[i]; if (historical_entry.block.major_version < cryptonote::network_version_10_bulletproofs) break; miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::FAKECHAIN, historical_entry.block, blk.major_version); } } } size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); std::optional> sn_rwds; if (hf_version_ >= cryptonote::network_version_19) { sn_rwds = sqlite_db_->get_sn_payments(height); //Rewards to pay out } uint64_t block_rewards = 0; bool r; while (true) { std::tie(r, block_rewards) = construct_miner_tx(height, tools::median(params.block_weights.begin(), params.block_weights.end()), params.prev.already_generated_coins, target_block_weight, total_fee, blk.miner_tx, miner_tx_context, sn_rwds, cryptonote::blobdata(), blk.major_version ); if (!r) return false; entry.block_weight = txs_weight + get_transaction_weight(blk.miner_tx); if (target_block_weight < entry.block_weight) { target_block_weight = entry.block_weight; } else if (entry.block_weight < target_block_weight) { size_t delta = target_block_weight - entry.block_weight; blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); entry.block_weight = txs_weight + get_transaction_weight(blk.miner_tx); if (entry.block_weight == target_block_weight) { break; } else { CHECK_AND_ASSERT_MES(target_block_weight < entry.block_weight, false, "Unexpected block size"); delta = entry.block_weight - target_block_weight; blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta); entry.block_weight = txs_weight + get_transaction_weight(blk.miner_tx); if (entry.block_weight == target_block_weight) { break; } else { CHECK_AND_ASSERT_MES(entry.block_weight < target_block_weight, false, "Unexpected block size"); blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); } } } else { break; } } blk.reward = block_rewards; entry.txs = tx_list; uint64_t block_reward, block_reward_unpenalized; cryptonote::get_base_block_reward(tools::median(params.block_weights.begin(), params.block_weights.end()), entry.block_weight, params.prev.already_generated_coins, block_reward, block_reward_unpenalized, params.hf_version, height); entry.already_generated_coins = block_reward + params.prev.already_generated_coins; // NOTE: This relies on the block hash, so must be done after if (make_pulse_block) { crypto::hash block_hash = cryptonote::get_block_hash(blk); assert(blk.signatures.empty()); // NOTE: Fill Pulse Signature Data for (size_t i = 0; i < service_nodes::PULSE_BLOCK_REQUIRED_SIGNATURES; i++) { service_nodes::service_node_keys validator_keys = get_cached_keys(pulse_quorum.validators[i]); assert(validator_keys.pub == pulse_quorum.validators[i]); service_nodes::quorum_signature signature = {}; signature.voter_index = i; crypto::generate_signature(block_hash, validator_keys.pub, validator_keys.key, signature.signature); blk.signatures.push_back(signature); } } return true; } void oxen_chain_generator::block_end(oxen_blockchain_entry &entry, oxen_create_block_params const ¶ms) const { entry.service_node_state = params.prev.service_node_state; entry.service_node_state.update_from_block(db_, cryptonote::FAKECHAIN, state_history_, {} /*state_archive*/, {} /*alt_states*/, entry.block, entry.txs, nullptr); } bool oxen_chain_generator::process_registration_tx(cryptonote::transaction& tx, uint64_t block_height, uint8_t hf_version) { service_nodes::contributor_args_t contributor_args = {}; crypto::public_key service_node_key; uint64_t expiration_timestamp{0}; crypto::signature signature; if (!service_nodes::reg_tx_extract_fields(tx, contributor_args, expiration_timestamp, service_node_key, signature)) return false; uint64_t staking_requirement = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, block_height); return true; } bool oxen_chain_generator::create_block(oxen_blockchain_entry &entry, oxen_create_block_params ¶ms, const std::vector &tx_list) const { if (!block_begin(entry, params, tx_list)) return false; if (entry.block.signatures.empty()) fill_nonce_with_oxen_generator(this, entry.block, TEST_DEFAULT_DIFFICULTY, cryptonote::get_block_height(entry.block)); block_end(entry, params); return true; } oxen_create_block_params oxen_chain_generator::next_block_params() const { oxen_blockchain_entry const &prev = top(); uint64_t next_height = height() + 1; oxen_create_block_params result = {}; result.prev = prev; result.miner_acc = first_miner_; result.timestamp = prev.block.timestamp + tools::to_seconds(TARGET_BLOCK_TIME); result.block_weights = last_n_block_weights(height(), CRYPTONOTE_REWARD_BLOCKS_WINDOW); result.hf_version = get_hf_version_at(next_height); result.block_leader = prev.service_node_state.get_block_leader(); result.total_fee = 0; // Request chain generator to calculate the fee return result; } oxen_blockchain_entry oxen_chain_generator::create_next_block(const std::vector& txs, cryptonote::checkpoint_t const *checkpoint) { oxen_blockchain_entry result = {}; oxen_create_block_params block_params = next_block_params(); create_block(result, block_params, txs); if (checkpoint) { result.checkpoint = *checkpoint; result.checkpointed = true; } hf_version_ = result.block.major_version; return result; } uint8_t oxen_chain_generator::get_hf_version_at(uint64_t height) const { uint8_t cur_hf_ver = 0; for (auto i = 0u; i < hard_forks_.size(); ++i) { if (height < hard_forks_[i].height) break; cur_hf_ver = hard_forks_[i].version; } assert(cur_hf_ver != 0); return cur_hf_ver; } std::vector oxen_chain_generator::last_n_block_weights(uint64_t height, uint64_t num) const { std::vector result; if (num > height) num = height; result.reserve(num); assert(height < db_.blocks.size()); for (size_t i = 0; i < num; i++) { uint64_t index = height - num + i; result.push_back(db_.blocks[index].block_weight); if ((height - i) == 0) break; } return result; } /// -------------------------------------------------------------- void test_generator::get_block_chain(std::vector& blockchain, const crypto::hash& head, size_t n) const { crypto::hash curr = head; while (crypto::null_hash != curr && blockchain.size() < n) { auto it = m_blocks_info.find(curr); if (m_blocks_info.end() == it) { throw std::runtime_error("block hash wasn't found"); } blockchain.push_back(it->second); curr = it->second.prev_id; } std::reverse(blockchain.begin(), blockchain.end()); } // TODO(oxen): Copypasta void test_generator::get_block_chain(std::vector &blockchain, const crypto::hash &head, size_t n) const { crypto::hash curr = head; while (crypto::null_hash != curr && blockchain.size() < n) { auto it = m_blocks_info.find(curr); if (m_blocks_info.end() == it) { throw std::runtime_error("block hash wasn't found"); } blockchain.push_back(it->second.block); curr = it->second.prev_id; } std::reverse(blockchain.begin(), blockchain.end()); } void test_generator::get_last_n_block_weights(std::vector& block_weights, const crypto::hash& head, size_t n) const { std::vector blockchain; get_block_chain(blockchain, head, n); for (auto& bi : blockchain) { block_weights.push_back(bi.block_weight); } } uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) const { auto it = m_blocks_info.find(blk_id); if (it == m_blocks_info.end()) throw std::runtime_error("block hash wasn't found"); return it->second.already_generated_coins; } uint64_t test_generator::get_already_generated_coins(const cryptonote::block& blk) const { crypto::hash blk_hash; get_block_hash(blk, blk_hash); return get_already_generated_coins(blk_hash); } void test_generator::add_block(const cryptonote::block& blk, size_t txs_weight, std::vector& block_weights, uint64_t already_generated_coins) { const size_t block_weight = txs_weight + get_transaction_weight(blk.miner_tx); uint64_t block_reward, block_reward_unpenalized; cryptonote::get_base_block_reward(tools::median(block_weights.begin(), block_weights.end()), block_weight, already_generated_coins, block_reward, block_reward_unpenalized, m_hf_version, 0); m_blocks_info.insert({get_block_hash(blk), block_info(blk.prev_id, already_generated_coins + block_reward, block_weight, blk)}); } static void manual_calc_batched_governance(const test_generator &generator, const crypto::hash &head, cryptonote::oxen_miner_tx_context &miner_tx_context, int hard_fork_version, uint64_t height) { miner_tx_context.batched_governance = 0; if (hard_fork_version >= cryptonote::network_version_10_bulletproofs && cryptonote::height_has_governance_output(cryptonote::FAKECHAIN, hard_fork_version, height)) { uint64_t num_blocks = cryptonote::get_config(cryptonote::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; uint64_t start_height = height - num_blocks; if (hard_fork_version >= cryptonote::network_version_15_ons) { miner_tx_context.batched_governance = num_blocks * cryptonote::governance_reward_formula(hard_fork_version); return; } if (height < num_blocks) { start_height = 0; num_blocks = height; } std::vector blockchain; blockchain.reserve(num_blocks); generator.get_block_chain(blockchain, head, num_blocks); for (const cryptonote::block &entry : blockchain) { uint64_t block_height = cryptonote::get_block_height(entry); if (block_height < start_height) continue; if (entry.major_version >= cryptonote::network_version_10_bulletproofs) miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::FAKECHAIN, entry, hard_fork_version); } } } bool test_generator::construct_block(cryptonote::block &blk, uint64_t height, const crypto::hash &prev_id, const cryptonote::account_base &miner_acc, uint64_t timestamp, uint64_t already_generated_coins, std::vector &block_weights, const std::list &tx_list, const service_nodes::payout &block_leader) { /// a temporary workaround blk.major_version = m_hf_version; blk.minor_version = m_hf_version; blk.timestamp = timestamp; blk.prev_id = prev_id; blk.tx_hashes.reserve(tx_list.size()); for (const cryptonote::transaction &tx : tx_list) { crypto::hash tx_hash; cryptonote::get_transaction_hash(tx, tx_hash); blk.tx_hashes.push_back(tx_hash); } uint64_t total_fee = 0; size_t txs_weight = 0; for (auto& tx : tx_list) { uint64_t fee = 0; bool r = get_tx_miner_fee(tx, fee, blk.major_version >= HF_VERSION_FEE_BURNING); CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); total_fee += fee; txs_weight += get_transaction_weight(tx); } auto miner_tx_context = cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc.get_keys().m_account_address, block_leader); blk.miner_tx = {}; size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height); uint64_t block_rewards = 0; bool r; std::optional> sn_rwds; while (true) { std::tie(r, block_rewards) = construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, target_block_weight, total_fee, blk.miner_tx, miner_tx_context, sn_rwds, cryptonote::blobdata(), m_hf_version); if (!r) return false; size_t actual_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); if (target_block_weight < actual_block_weight) { target_block_weight = actual_block_weight; } else if (actual_block_weight < target_block_weight) { size_t delta = target_block_weight - actual_block_weight; blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); actual_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); if (actual_block_weight == target_block_weight) { break; } else { CHECK_AND_ASSERT_MES(target_block_weight < actual_block_weight, false, "Unexpected block size"); delta = actual_block_weight - target_block_weight; blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta); actual_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); if (actual_block_weight == target_block_weight) { break; } else { CHECK_AND_ASSERT_MES(actual_block_weight < target_block_weight, false, "Unexpected block size"); blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); } } } else { break; } } //blk.tree_root_hash = get_tx_tree_hash(blk); fill_nonce_with_test_generator(this, blk, TEST_DEFAULT_DIFFICULTY, height); add_block(blk, txs_weight, block_weights, already_generated_coins); return true; } bool test_generator::construct_block(cryptonote::block &blk, const cryptonote::account_base &miner_acc, uint64_t timestamp) { std::vector block_weights; std::list tx_list; return construct_block(blk, 0, crypto::null_hash, miner_acc, timestamp, 0, block_weights, tx_list); } bool test_generator::construct_block(cryptonote::block &blk, const cryptonote::block &blk_prev, const cryptonote::account_base &miner_acc, const std::list &tx_list /* = {}*/, const service_nodes::payout &block_leader) { uint64_t height = var::get(blk_prev.miner_tx.vin.front()).height + 1; crypto::hash prev_id = get_block_hash(blk_prev); // Keep difficulty unchanged uint64_t timestamp = blk_prev.timestamp + tools::to_seconds(TARGET_BLOCK_TIME); uint64_t already_generated_coins = get_already_generated_coins(prev_id); std::vector block_weights; get_last_n_block_weights(block_weights, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW); return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_weights, tx_list, block_leader); } bool test_generator::construct_block_manually( cryptonote::block &blk, const cryptonote::block &prev_block, const cryptonote::account_base &miner_acc, int actual_params /* = bf_none*/, uint8_t major_ver /* = 0*/, uint8_t minor_ver /* = 0*/, uint64_t timestamp /* = 0*/, const crypto::hash &prev_id /* = crypto::hash()*/, const cryptonote::difficulty_type &diffic /* = 1*/, const cryptonote::transaction &miner_tx /* = transaction()*/, const std::vector &tx_hashes /* = std::vector()*/, size_t txs_weight /* = 0*/, size_t miner_fee /*= 0*/) { blk.major_version = actual_params & bf_major_ver ? major_ver : static_cast(cryptonote::network_version_7); blk.minor_version = actual_params & bf_minor_ver ? minor_ver : static_cast(cryptonote::network_version_7); blk.timestamp = actual_params & bf_timestamp ? timestamp : prev_block.timestamp + tools::to_seconds(TARGET_BLOCK_TIME); // Keep difficulty unchanged blk.prev_id = actual_params & bf_prev_id ? prev_id : get_block_hash(prev_block); blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector(); size_t height = get_block_height(prev_block) + 1; blk.height = height; uint64_t already_generated_coins = get_already_generated_coins(prev_block); std::vector block_weights; get_last_n_block_weights(block_weights, get_block_hash(prev_block), CRYPTONOTE_REWARD_BLOCKS_WINDOW); if (actual_params & bf_miner_tx) { blk.miner_tx = miner_tx; } else { // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE cryptonote::oxen_miner_tx_context miner_tx_context = {}; miner_tx_context.nettype = cryptonote::FAKECHAIN; manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height); std::optional> sn_rwds; size_t current_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); uint64_t block_rewards = 0; bool r; std::tie(r, block_rewards) = construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, current_block_weight, miner_fee, blk.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc.get_keys().m_account_address), sn_rwds, cryptonote::blobdata(), m_hf_version); if (!r) return false; } //blk.tree_root_hash = get_tx_tree_hash(blk); cryptonote::difficulty_type a_diffic = actual_params & bf_diffic ? diffic : TEST_DEFAULT_DIFFICULTY; fill_nonce_with_test_generator(this, blk, a_diffic, height); add_block(blk, txs_weight, block_weights, already_generated_coins); return true; } bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, const cryptonote::account_base& miner_acc, const std::vector& tx_hashes, size_t txs_weight) { return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, cryptonote::transaction(), tx_hashes, txs_weight, 0); } cryptonote::transaction make_registration_tx(std::vector& events, const cryptonote::account_base& account, const cryptonote::keypair& service_node_keys, uint64_t operator_cut, const std::vector& contributors, const std::vector& portions, const cryptonote::block& head, uint8_t hf_version) { const auto new_height = cryptonote::get_block_height(head) + 1; const auto staking_requirement = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, new_height); uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement); cryptonote::transaction tx; uint64_t unlock_time = 0; if (hf_version < cryptonote::network_version_11_infinite_staking) unlock_time = new_height + service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN); std::vector extra; cryptonote::add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub); const uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW; crypto::hash hash; if (!cryptonote::get_registration_hash(contributors, operator_cut, portions, exp_timestamp, hash)) { MERROR("Could not make registration hash from addresses and portions"); return {}; } crypto::signature signature; crypto::generate_signature(hash, service_node_keys.pub, service_node_keys.sec, signature); add_service_node_register_to_tx_extra(extra, contributors, operator_cut, portions, exp_timestamp, signature); add_service_node_contributor_to_tx_extra(extra, contributors.at(0)); cryptonote::txtype tx_type = cryptonote::txtype::standard; if (hf_version >= cryptonote::network_version_15_ons) tx_type = cryptonote::txtype::stake; // NOTE: txtype stake was not introduced until HF14 oxen_tx_builder(events, tx, head, account, account.get_keys().m_account_address, amount, hf_version).with_tx_type(tx_type).with_extra(extra).with_unlock_time(unlock_time).build(); events.push_back(tx); return tx; } namespace { uint64_t get_inputs_amount(const std::vector &s) { uint64_t r = 0; for (const cryptonote::tx_source_entry &e : s) { r += e.amount; } return r; } } uint64_t get_amount(const cryptonote::account_base& account, const cryptonote::transaction& tx, rct::key& mask, int i) { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); crypto::key_derivation derivation; if (!crypto::generate_key_derivation(tx_pub_key, account.get_keys().m_view_secret_key, derivation)) return 0; if (!std::holds_alternative(tx.vout[i].target)) return 0; hw::device& hwdev = hw::get_device("default"); uint64_t money_transferred = 0; crypto::secret_key scalar1; hwdev.derivation_to_scalar(derivation, i, scalar1); try { if (rct::is_rct_simple(tx.rct_signatures.type)) money_transferred = rct::decodeRctSimple(tx.rct_signatures, rct::sk2rct(scalar1), i, mask, hwdev); else if (tx.rct_signatures.type == rct::RCTType::Full) money_transferred = rct::decodeRct(tx.rct_signatures, rct::sk2rct(scalar1), i, hwdev); else if (tx.rct_signatures.type == rct::RCTType::Null) money_transferred = tx.vout[i].amount; else { LOG_PRINT_L0(__func__ << ": Unsupported rct type: " << (int)tx.rct_signatures.type); return 0; } } catch (const std::exception &e) { LOG_PRINT_L0("Failed to decode input " << i << ": " << e.what()); return 0; } return money_transferred; } uint64_t get_amount(const cryptonote::account_base& account, const cryptonote::transaction& tx, int i) { rct::key mask_unused; return get_amount(account, tx, mask_unused, i); } bool init_output_indices(std::vector& outs, std::vector& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { for (const cryptonote::block& blk : blockchain) { std::vector vtx; vtx.push_back(&blk.miner_tx); for(const crypto::hash &h : blk.tx_hashes) { const auto cit = mtx.find(h); if (mtx.end() == cit) throw std::runtime_error("block contains an unknown tx hash"); vtx.push_back(cit->second); } for (size_t i = 0; i < vtx.size(); i++) { const cryptonote::transaction &tx = *vtx[i]; for (size_t j = 0; j < tx.vout.size(); ++j) { const cryptonote::tx_out &out = tx.vout[j]; if (std::holds_alternative(out.target)) { const auto height = var::get(blk.miner_tx.vin.front()).height; output_index oi(out.target, out.amount, height, i, j, &blk, vtx[i]); oi.unlock_time = (tx.version < cryptonote::txversion::v3_per_output_unlock_times) ? tx.unlock_time : tx.output_unlock_times[j]; oi.idx = outs.size(); oi.mask = rct::zeroCommit(out.amount); oi.is_coin_base = (i == 0); oi.deterministic_key_pair = false; oi.set_rct(tx.version >= cryptonote::txversion::v2_ringct); const auto gov_key = cryptonote::get_deterministic_keypair_from_height(height); bool account_received_money = is_out_to_acc(from.get_keys(), var::get(out.target), gov_key.pub, {}, j); if (account_received_money) oi.deterministic_key_pair = true; if (!account_received_money) account_received_money = is_out_to_acc(from.get_keys(), var::get(out.target), cryptonote::get_tx_pub_key_from_extra(tx), cryptonote::get_additional_tx_pub_keys_from_extra(tx), j); if (account_received_money) { outs_mine.push_back(oi.idx); if (oi.amount == 0) { assert(oi.is_coin_base == false); oi.amount = get_amount(from, tx, j); oi.mask = tx.rct_signatures.outPk[j].mask; } } outs.push_back(oi); } } } } return true; } bool init_spent_output_indices(std::vector& outs, const std::vector& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { if (mtx.empty()) { // NOTE: There are no transactions, so outputs haven't been spent yet (i.e. a blockchain with strictly just rewards) return true; } for (size_t out_idx : outs_mine) { output_index& oi = outs[out_idx]; // construct key image for this output crypto::key_image img; cryptonote::keypair in_ephemeral; crypto::public_key out_key = var::get(oi.out).key; std::unordered_map subaddresses; subaddresses[from.get_keys().m_account_address.m_spend_public_key] = {0,0}; const auto tx_pk = oi.deterministic_key_pair ? cryptonote::get_deterministic_keypair_from_height(oi.blk_height).pub : get_tx_pub_key_from_extra(*oi.p_tx); generate_key_image_helper(from.get_keys(), subaddresses, out_key, tx_pk, get_additional_tx_pub_keys_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img, hw::get_device(("default"))); // lookup for this key image in the events vector for (auto& tx_pair: mtx) { const cryptonote::transaction& tx = *tx_pair.second; for (const cryptonote::txin_v &in : tx.vin) { if (std::holds_alternative(in)) { const auto &itk = var::get(in); if (itk.k_image == img) { oi.spent = true; } } } } } return true; } static bool fill_output_entries(const std::vector& out_indices, size_t sender_out, size_t nmix, size_t& real_entry_idx, std::vector& output_entries) { if (out_indices.size() <= nmix) return false; bool sender_out_found = false; size_t rest = nmix; for (size_t i = 0; i < out_indices.size() && (0 < rest || !sender_out_found); ++i) { const output_index& oi = out_indices[i]; if (oi.spent) continue; bool append = false; if (i == sender_out) { append = true; sender_out_found = true; real_entry_idx = output_entries.size(); } else if (0 < rest) { --rest; append = true; } if (append) { rct::key comm = oi.commitment(); const cryptonote::txout_to_key& otk = var::get(oi.out); output_entries.push_back(cryptonote::tx_source_entry::output_entry(oi.idx, rct::ctkey({rct::pk2rct(otk.key), comm}))); } } return 0 == rest && sender_out_found; } bool fill_tx_sources(std::vector& sources, const std::vector& events, const cryptonote::block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix) { /// Don't fill up sources if the amount is zero if (amount == 0) return true; std::vector outs; std::vector outs_mine; std::vector blockchain; map_hash2tx_t mtx; if (!find_block_chain(events, blockchain, mtx, cryptonote::get_block_hash(blk_head))) return false; if (!init_output_indices(outs, outs_mine, blockchain, mtx, from)) return false; if (!init_spent_output_indices(outs, outs_mine, blockchain, mtx, from)) return false; uint64_t sources_amount = 0; bool sources_found = false; for (const size_t sender_out : outs_mine) { const output_index& oi = outs[sender_out]; if (oi.spent) continue; if (!cryptonote::rules::is_output_unlocked(oi.unlock_time, cryptonote::get_block_height(blk_head))) continue; cryptonote::tx_source_entry ts; const auto& tx = *oi.p_tx; ts.amount = oi.amount; ts.real_output_in_tx_index = oi.out_no; ts.real_out_tx_key = get_tx_pub_key_from_extra(tx); // incoming tx public key ts.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(tx); ts.mask = rct::identity(); ts.rct = true; rct::key comm = rct::zeroCommit(ts.amount); for(auto & ot : ts.outputs) ot.second.mask = comm; /// Filling in the mask { crypto::key_derivation derivation; bool r = crypto::generate_key_derivation(ts.real_out_tx_key, from.get_keys().m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); crypto::secret_key amount_key; crypto::derivation_to_scalar(derivation, oi.out_no, amount_key); if (rct::is_rct_simple(tx.rct_signatures.type)) { rct::decodeRctSimple(tx.rct_signatures, rct::sk2rct(amount_key), oi.out_no, ts.mask, hw::get_device("default")); } else if (tx.rct_signatures.type == rct::RCTType::Full) { rct::decodeRct(tx.rct_signatures, rct::sk2rct(amount_key), oi.out_no, ts.mask, hw::get_device("default")); } } if (!fill_output_entries(outs, sender_out, nmix, ts.real_output, ts.outputs)) continue; sources.push_back(ts); sources_amount += ts.amount; sources_found = amount <= sources_amount; if (sources_found) return true; } return false; } bool fill_tx_destination(cryptonote::tx_destination_entry &de, const cryptonote::account_public_address &to, uint64_t amount) { de.addr = to; de.amount = amount; return true; } void fill_tx_sources_and_multi_destinations(const std::vector& events, const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_public_address& to, uint64_t const *amount, int num_amounts, uint64_t fee, size_t nmix, std::vector& sources, std::vector& destinations, bool always_add_change_ouput, uint64_t *change_amount) { sources.clear(); destinations.clear(); uint64_t total_amount = fee; for (int i = 0; i < num_amounts; ++i) total_amount += amount[i]; if (!fill_tx_sources(sources, events, blk_head, from, total_amount, nmix)) { throw std::runtime_error("couldn't fill transaction sources"); } for (int i = 0; i < num_amounts; ++i) { cryptonote::tx_destination_entry de; if (!fill_tx_destination(de, to, amount[i])) throw std::runtime_error("couldn't fill transaction destination"); destinations.push_back(de); } cryptonote::tx_destination_entry de_change; uint64_t cash_back = get_inputs_amount(sources) - (total_amount); if (0 < cash_back || always_add_change_ouput) { if (!fill_tx_destination(de_change, from.get_keys().m_account_address, cash_back)) throw std::runtime_error("couldn't fill transaction cache back destination"); destinations.push_back(de_change); } if (change_amount) *change_amount = (cash_back > 0) ? cash_back : 0; } map_txid_output_t::iterator block_tracker::find_out(const crypto::hash &txid, size_t out) { return find_out(std::make_pair(txid, out)); } map_txid_output_t::iterator block_tracker::find_out(const output_hasher &id) { return m_map_outs.find(id); } void block_tracker::process(const std::vector& blockchain, const map_hash2tx_t& mtx) { std::vector blks; blks.reserve(blockchain.size()); for (const cryptonote::block &blk : blockchain) { auto hsh = cryptonote::get_block_hash(blk); auto it = m_blocks.find(hsh); if (it == m_blocks.end()){ m_blocks[hsh] = blk; } blks.push_back(&m_blocks[hsh]); } process(blks, mtx); } void block_tracker::process(const std::vector& blockchain, const map_hash2tx_t& mtx) { for (const cryptonote::block *blk : blockchain) { std::vector vtx; vtx.push_back(&(blk->miner_tx)); for(const crypto::hash &h : blk->tx_hashes) { const map_hash2tx_t::const_iterator cit = mtx.find(h); CHECK_AND_ASSERT_THROW_MES(mtx.end() != cit, "block contains an unknown tx hash"); vtx.push_back(cit->second); } for (size_t i = 0; i < vtx.size(); i++) { process(blk, vtx[i], i); } } } void block_tracker::process(const cryptonote::block *blk, const cryptonote::transaction *tx, size_t i) { for (size_t j = 0; j < tx->vout.size(); ++j) { const cryptonote::tx_out &out = tx->vout[j]; if (!std::holds_alternative(out.target)) { // out_to_key continue; } const uint64_t rct_amount = tx->version == cryptonote::txversion::v2_ringct ? 0 : out.amount; const output_hasher hid = std::make_pair(tx->hash, j); auto it = find_out(hid); if (it != m_map_outs.end()){ continue; } output_index oi(out.target, out.amount, var::get(blk->miner_tx.vin.front()).height, i, j, blk, tx); oi.set_rct(tx->version == cryptonote::txversion::v2_ringct); oi.idx = m_outs[rct_amount].size(); oi.unlock_time = tx->unlock_time; oi.is_coin_base = tx->vin.size() == 1 && std::holds_alternative(tx->vin.back()); m_outs[rct_amount].push_back(oi); m_map_outs.insert({hid, oi}); } } void block_tracker::global_indices(const cryptonote::transaction *tx, std::vector &indices) { indices.clear(); for(size_t j=0; j < tx->vout.size(); ++j){ auto it = find_out(tx->hash, j); if (it != m_map_outs.end()){ indices.push_back(it->second.idx); } } } void block_tracker::get_fake_outs(size_t num_outs, uint64_t amount, uint64_t global_index, uint64_t cur_height, std::vector &outs){ auto & vct = m_outs[amount]; const size_t n_outs = vct.size(); CHECK_AND_ASSERT_THROW_MES(n_outs > 0, "n_outs is 0"); std::set used; std::vector choices; choices.resize(n_outs); for(size_t i=0; i < n_outs; ++i) choices[i] = i; shuffle(choices.begin(), choices.end(), std::default_random_engine(crypto::rand())); size_t n_iters = 0; ssize_t idx = -1; outs.reserve(num_outs); while(outs.size() < num_outs){ n_iters += 1; idx = (idx + 1) % n_outs; size_t oi_idx = choices[(size_t)idx]; CHECK_AND_ASSERT_THROW_MES((n_iters / n_outs) <= outs.size(), "Fake out pick selection problem"); auto & oi = vct[oi_idx]; if (oi.idx == global_index) continue; if (!std::holds_alternative(oi.out)) continue; if (oi.unlock_time > cur_height) continue; if (used.find(oi_idx) != used.end()) continue; rct::key comm = oi.commitment(); auto out = var::get(oi.out); auto item = std::make_tuple(oi.idx, out.key, comm); outs.push_back(item); used.insert(oi_idx); } } std::string block_tracker::dump_data() { std::ostringstream ss; for (auto &m_out : m_outs) { auto & vct = m_out.second; ss << m_out.first << " => |vector| = " << vct.size() << '\n'; for (const auto & oi : vct) { auto out = var::get(oi.out); ss << " idx: " << oi.idx << ", rct: " << oi.rct << ", xmr: " << oi.amount << ", key: " << dump_keys(out.key.data) << ", msk: " << dump_keys(oi.comm.bytes) << ", txid: " << dump_keys(oi.p_tx->hash.data) << '\n'; } } return ss.str(); } void block_tracker::dump_data(const std::string & fname) { std::ofstream myfile; myfile.open (fname); myfile << dump_data(); myfile.close(); } std::string dump_data(const cryptonote::transaction &tx) { std::ostringstream ss; ss << "msg: " << dump_keys(tx.rct_signatures.message.bytes) << ", vin: "; for(auto & in : tx.vin){ if (std::holds_alternative(in)){ auto tk = var::get(in); std::vector full_off; int64_t last = -1; ss << " i: " << tk.amount << " ["; for(auto ix : tk.key_offsets){ ss << ix << ", "; if (last == -1){ last = ix; full_off.push_back(ix); } else { last += ix; full_off.push_back((uint64_t)last); } } ss << "], full: ["; for(auto ix : full_off){ ss << ix << ", "; } ss << "]; "; } else if (std::holds_alternative(in)){ ss << " h: " << var::get(in).height << ", "; } else { ss << " ?, "; } } ss << ", mixring: \n"; for (const auto & row : tx.rct_signatures.mixRing){ for(auto cur : row){ ss << " (" << dump_keys(cur.dest.bytes) << ", " << dump_keys(cur.mask.bytes) << ")\n "; } ss << "; "; } return ss.str(); } cryptonote::account_public_address get_address(const var_addr_t& inp) { if (std::holds_alternative(inp)){ return var::get(inp); } else if (std::holds_alternative(inp)){ return var::get(inp).m_account_address; } else if (std::holds_alternative(inp)){ return var::get(inp).get_keys().m_account_address; } else if (std::holds_alternative(inp)){ return var::get(inp).addr; } else { throw std::runtime_error("Unexpected type"); } } uint64_t sum_amount(const std::vector& destinations) { uint64_t amount = 0; for(auto & cur : destinations){ amount += cur.amount; } return amount; } uint64_t sum_amount(const std::vector& sources) { uint64_t amount = 0; for(auto & cur : sources){ amount += cur.amount; } return amount; } void fill_tx_destinations(const var_addr_t& from, const std::vector& dests, uint64_t fee, const std::vector &sources, std::vector& destinations, bool always_change) { destinations.clear(); uint64_t amount = sum_amount(dests); std::copy(dests.begin(), dests.end(), std::back_inserter(destinations)); cryptonote::tx_destination_entry de_change; uint64_t cash_back = get_inputs_amount(sources) - (amount + fee); if (cash_back > 0 || always_change) { if (!fill_tx_destination(de_change, get_address(from), cash_back <= 0 ? 0 : cash_back)) throw std::runtime_error("couldn't fill transaction cache back destination"); destinations.push_back(de_change); } } void fill_tx_destinations(const var_addr_t& from, const cryptonote::account_public_address& to, uint64_t amount, uint64_t fee, const std::vector &sources, std::vector& destinations, std::vector& destinations_pure, bool always_change) { destinations.clear(); cryptonote::tx_destination_entry de; if (!fill_tx_destination(de, to, amount)) throw std::runtime_error("couldn't fill transaction destination"); destinations.push_back(de); destinations_pure.push_back(de); cryptonote::tx_destination_entry de_change; uint64_t cash_back = get_inputs_amount(sources) - (amount + fee); if (cash_back > 0 || always_change) { if (!fill_tx_destination(de_change, get_address(from), cash_back <= 0 ? 0 : cash_back)) throw std::runtime_error("couldn't fill transaction cache back destination"); destinations.push_back(de_change); } } void fill_tx_sources_and_destinations(const std::vector& events, const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_public_address& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector& sources, std::vector& destinations, uint64_t *change_amount) { uint64_t *amounts = &amount; int num_amounts = 1; fill_tx_sources_and_multi_destinations(events, blk_head, from, to, amounts, num_amounts, fee, nmix, sources, destinations, true /*always_add_change_output*/, change_amount); } void fill_tx_destinations(const var_addr_t& from, const cryptonote::account_public_address& to, uint64_t amount, uint64_t fee, const std::vector &sources, std::vector& destinations, bool always_change) { std::vector destinations_pure; fill_tx_destinations(from, to, amount, fee, sources, destinations, destinations_pure, always_change); } cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr, uint64_t amount) { cryptonote::tx_destination_entry de; de.amount = amount; de.addr = get_address(to); de.is_subaddress = is_subaddr; return de; } std::vector build_dsts(const var_addr_t& to1, bool sub1, uint64_t am1) { std::vector res; res.push_back(build_dst(to1, sub1, am1)); return res; } std::vector build_dsts(std::initializer_list inps) { std::vector res; res.reserve(inps.size()); for(auto & c : inps){ res.push_back(build_dst(c.addr, c.is_subaddr, c.amount)); } return res; } bool construct_tx_to_key(const std::vector& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, const cryptonote::account_base& from, const var_addr_t& to, uint64_t amount, uint64_t fee, size_t nmix, rct::RangeProofType range_proof_type, int bp_version) { std::vector sources; std::vector destinations; fill_tx_sources_and_destinations(events, blk_head, from, get_address(to), amount, fee, nmix, sources, destinations); cryptonote::tx_destination_entry change_addr; return construct_tx_rct(from.get_keys(), sources, destinations, change_addr, std::vector(), tx, 0, range_proof_type, bp_version); } bool construct_tx_to_key(const std::vector& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, const cryptonote::account_base& from, std::vector destinations, uint64_t fee, size_t nmix, rct::RangeProofType range_proof_type, int bp_version) { std::vector sources; std::vector destinations_all; uint64_t amount = sum_amount(destinations); if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) { throw std::runtime_error("couldn't fill transaction sources"); } fill_tx_destinations(from, destinations, fee, sources, destinations_all, true); cryptonote::tx_destination_entry change_addr; return construct_tx_rct(from.get_keys(), sources, destinations_all, change_addr, std::vector(), tx, 0, range_proof_type, bp_version); } bool construct_tx_to_key(cryptonote::transaction& tx, const cryptonote::account_base& from, const var_addr_t& to, uint64_t amount, std::vector &sources, uint64_t fee, rct::RangeProofType range_proof_type, int bp_version) { cryptonote::tx_destination_entry change_addr; std::vector destinations; fill_tx_destinations(from, get_address(to), amount, fee, sources, destinations); return construct_tx_rct(from.get_keys(), sources, destinations, change_addr, std::vector(), tx, 0, range_proof_type, bp_version); } bool construct_tx_to_key(cryptonote::transaction& tx, const cryptonote::account_base& from, const std::vector& destinations, std::vector &sources, uint64_t fee, rct::RangeProofType range_proof_type, int bp_version) { cryptonote::tx_destination_entry change_addr; std::vector all_destinations; fill_tx_destinations(from, destinations, fee, sources, all_destinations, true); return construct_tx_rct(from.get_keys(), sources, all_destinations, change_addr, std::vector(), tx, 0, range_proof_type, bp_version); } bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std::vector& sources, const std::vector& destinations, const std::optional& change_addr, std::vector extra, cryptonote::transaction& tx, uint64_t unlock_time, rct::RangeProofType range_proof_type, int bp_version) { std::unordered_map subaddresses; subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0, 0}; crypto::secret_key tx_key; std::vector additional_tx_keys; std::vector destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct_config, nullptr); } 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 tx; oxen_tx_builder(events, tx, blk_head, acc_from, acc_to.get_keys().m_account_address, amount, cryptonote::network_version_7).with_fee(fee).build(); 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::vector outs; std::vector outs_mine; map_hash2tx_t confirmed_txs; get_confirmed_txs(blockchain, mtx, confirmed_txs); if (!init_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) return false; if (!init_spent_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) return false; for (const size_t out_idx : outs_mine) { if (outs[out_idx].spent) continue; res += outs[out_idx].amount; } return res; } uint64_t get_unlocked_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx) { if (blockchain.empty()) return 0; uint64_t res = 0; std::vector outs; std::vector outs_mine; map_hash2tx_t confirmed_txs; get_confirmed_txs(blockchain, mtx, confirmed_txs); if (!init_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) return false; if (!init_spent_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) return false; for (const size_t out_idx : outs_mine) { const auto unlocked = cryptonote::rules::is_output_unlocked(outs[out_idx].unlock_time, get_block_height(blockchain.back())); if (outs[out_idx].spent || !unlocked) continue; res += outs[out_idx].amount; } return res; } bool extract_hard_forks(const std::vector& events, std::vector& hard_forks) { for(auto & ev : events) { if (std::holds_alternative(ev)) { const auto & rep_settings = var::get(ev); if (rep_settings.hard_forks) { const auto & hf = *rep_settings.hard_forks; std::copy(hf.begin(), hf.end(), std::back_inserter(hard_forks)); } } } return !hard_forks.empty(); } void get_confirmed_txs(const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) { std::unordered_set confirmed_hashes; for (const auto& blk : blockchain) { for (const auto& tx_hash : blk.tx_hashes) { confirmed_hashes.insert(tx_hash); } } for (const auto& tx_pair : mtx) { if (0 != confirmed_hashes.count(tx_pair.first)) { confirmed_txs.insert(tx_pair); } } } bool trim_block_chain(std::vector& blockchain, const crypto::hash& tail){ size_t cut = 0; bool found = true; for(size_t i = 0; i < blockchain.size(); ++i){ crypto::hash chash = get_block_hash(blockchain[i]); if (chash == tail){ cut = i; found = true; break; } } if (found && cut > 0){ blockchain.erase(blockchain.begin(), blockchain.begin() + cut); } return found; } bool trim_block_chain(std::vector& blockchain, const crypto::hash& tail){ size_t cut = 0; bool found = true; for(size_t i = 0; i < blockchain.size(); ++i){ crypto::hash chash = get_block_hash(*blockchain[i]); if (chash == tail){ cut = i; found = true; break; } } if (found && cut > 0){ blockchain.erase(blockchain.begin(), blockchain.begin() + cut); } return found; } uint64_t num_blocks(const std::vector& events) { uint64_t res = 0; for (const test_event_entry& ev : events) { if (std::holds_alternative(ev)) { res += 1; } } return res; } cryptonote::block get_head_block(const std::vector& events) { for(auto it = events.rbegin(); it != events.rend(); ++it) { auto &ev = *it; if (std::holds_alternative(ev)) { return var::get(ev); } } throw std::runtime_error("No block event"); } bool find_block_chain(const std::vector &events, std::vector &blockchain, map_hash2tx_t &mtx, const crypto::hash &head) { std::unordered_map block_index; for (const test_event_entry &ev : events) { if (std::holds_alternative(ev)) { const auto *blk = &var::get(ev); block_index[get_block_hash(*blk)] = blk; } else if (std::holds_alternative>(ev)) { const auto *blk = &var::get>(ev); block_index[get_block_hash(blk->data.block)] = &blk->data.block; } else if (std::holds_alternative>(ev)) { const auto *blk = &var::get>(ev); block_index[get_block_hash(blk->data)] = &blk->data; } else if (std::holds_alternative(ev)) { const auto &tx = var::get(ev); mtx[get_transaction_hash(tx)] = &tx; } else if (std::holds_alternative>(ev)) { const auto &entry = var::get>(ev); mtx[get_transaction_hash(entry.data.tx)] = &entry.data.tx; } } bool b_success = false; crypto::hash id = head; for (auto it = block_index.find(id); block_index.end() != it; it = block_index.find(id)) { blockchain.push_back(*it->second); id = it->second->prev_id; if (crypto::null_hash == id) { b_success = true; break; } } reverse(blockchain.begin(), blockchain.end()); return b_success; } bool find_block_chain(const std::vector &events, std::vector &blockchain, map_hash2tx_t &mtx, const crypto::hash &head) { std::unordered_map block_index; for (const test_event_entry &ev : events) { if (std::holds_alternative(ev) || std::holds_alternative>(ev) || std::holds_alternative>(ev)) { if (std::holds_alternative(ev)) { const auto *blk = &var::get(ev); block_index[get_block_hash(*blk)] = blk; } else if (std::holds_alternative>(ev)) { const auto *blk = &var::get>(ev); block_index[get_block_hash(blk->data.block)] = &blk->data.block; } else if (std::holds_alternative>(ev)) { const auto *blk = &var::get>(ev); block_index[get_block_hash(blk->data)] = &blk->data; } } else if (std::holds_alternative(ev) || std::holds_alternative>(ev)) { if (std::holds_alternative(ev)) { const auto &tx = var::get(ev); mtx[get_transaction_hash(tx)] = &tx; } else if (std::holds_alternative>(ev)) { const auto &entry = var::get>(ev); mtx[get_transaction_hash(entry.data.tx)] = &entry.data.tx; } } } bool b_success = false; crypto::hash id = head; for (auto it = block_index.find(id); block_index.end() != it; it = block_index.find(id)) { blockchain.push_back(it->second); id = it->second->prev_id; if (crypto::null_hash == id) { b_success = true; break; } } reverse(blockchain.begin(), blockchain.end()); return b_success; } void test_chain_unit_base::register_callback(const std::string& cb_name, verify_callback cb) { m_callbacks[cb_name] = cb; } bool test_chain_unit_base::verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector &events) { auto cb_it = m_callbacks.find(cb_name); if(cb_it == m_callbacks.end()) { LOG_ERROR("Failed to find callback " << cb_name); return false; } return cb_it->second(c, ev_index, events); } bool test_chain_unit_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) { return !bvc.m_verifivation_failed; } bool test_chain_unit_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool /*tx_added*/, size_t /*event_index*/, const cryptonote::transaction& /*tx*/) { return !tvc.m_verifivation_failed; } bool test_chain_unit_base::check_tx_verification_context_array(const std::vector& tvcs, size_t /*tx_added*/, size_t /*event_index*/, const std::vector& /*txs*/) { for (const cryptonote::tx_verification_context &tvc: tvcs) if (tvc.m_verifivation_failed) return false; return true; }