diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 160f72a47..3de937ae0 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -247,6 +247,13 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } } add_tx_amount_output_indices(tx_id, amount_output_indices); + + // Check to see if this is a YIELD TX + if (tx.type == cryptonote::transaction_type::YIELD) { + + // We now need to insert a record into the "yield_tx_data" table to record the TX + + } } uint64_t BlockchainDB::add_block( const std::pair& blck @@ -255,6 +262,7 @@ uint64_t BlockchainDB::add_block( const std::pair& blck , const difficulty_type& cumulative_difficulty , const uint64_t& coins_generated , const std::vector>& txs + , const cryptonote::network_type& nettype ) { const block &blk = blck.first; @@ -295,6 +303,7 @@ uint64_t BlockchainDB::add_block( const std::pair& blck } std::map slippage_counts; + uint64_t yield_total = 0; if (blk.protocol_tx.version == 2) { num_rct_outs += blk.protocol_tx.vout.size(); @@ -333,44 +342,60 @@ uint64_t BlockchainDB::add_block( const std::pair& blck num_rct_outs_by_asset_type.add(asset_type, 1); } - // Update the amount tallies by ADDING the burnt amount + // Is this a CONVERT TX? if (tx.first.type == cryptonote::transaction_type::CONVERT) { + // Update the amount tallies by ADDING the burnt amount if (slippage_counts.count(asset_type) == 0) slippage_counts[asset_type] = 0; slippage_counts[asset_type] += tx.first.amount_burnt; } + + // Is this a YIELD TX? + if (tx.first.type == cryptonote::transaction_type::YIELD) { + yield_total += tx.first.amount_burnt; + } } ++tx_i; } // SRCG: This is the code that calculates the total slippage for the block // Now convert all of the residual balances into FULM - /* + boost::multiprecision::int128_t slippage_total_128 = 0; uint64_t slippage_total = 0; for (const auto& tally: slippage_counts) { - if (tally.second < 0) - throw std::runtime_error("Found a negative tally when summing the burnt/minted amounts"); - uint64_t slippage_amount = 0; + boost::multiprecision::int128_t slippage_amount_128 = 0; if (tally.first == "FULM") { - slippage_amount = tally.second; + slippage_amount_128 = tally.second; } else { - // Sanity check - do we have a price for this asset type in the PR? - if (blk.pricing_record.count(tally.first) == 0) { + // Sanity check - do we have a price for both source asset type and FULM in the PR? + boost::multiprecision::int128_t fulm_price = blk.pricing_record["FULM"]; + boost::multiprecision::int128_t asset_price = blk.pricing_record[tally.first]; + if (fulm_price == 0) { // No price available - bail out, because block is invalid - throw std::runtime_error("Asset type is not present in available pricing record:" + tally.first); + throw std::runtime_error("Asset type 'FULM' is not present in available pricing record"); } - // Convert the amount - //boost::multiprecision::uint128_t tally_128 = tally.second; + if (asset_price == 0) { + // No price available - bail out, because block is invalid + throw std::runtime_error("Asset type '" + tally.first + "' is not present in available pricing record"); + } + // Convert the amount into FULM + boost::multiprecision::int128_t tally_128 = tally.second; + tally_128 *= asset_price; + tally_128 /= fulm_price; + slippage_amount_128 = tally_128.convert_to(); } + slippage_total_128 += slippage_amount_128; } - */ + if (slippage_total_128 < 0) + throw std::runtime_error("Found a negative slippage total when summing the burnt/minted amounts"); + slippage_total = slippage_total_128.convert_to(); TIME_MEASURE_FINISH(time1); time_add_transaction += time1; // call out to subclass implementation to add the block & metadata time1 = epee::misc_utils::get_tick_count(); - add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, num_rct_outs_by_asset_type, blk_hash); + add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, num_rct_outs_by_asset_type, blk_hash, slippage_total, yield_total, nettype); TIME_MEASURE_FINISH(time1); time_add_block1 += time1; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index b2e054b5a..d9896fd78 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -197,6 +197,20 @@ struct txpool_tx_meta_t } }; +typedef struct yield_block_info { + uint64_t block_height; + uint64_t slippage_total; + uint64_t locked_coins; + uint64_t locked_coins_tally; + uint8_t network_health_percentage; +} yield_block_info; + +typedef struct yield_tx_info { + uint64_t block_height; + crypto::hash tx_hash; + uint64_t locked_coins; + crypto::public_key return_address; +} yield_tx_info; #define DBF_SAFE 1 #define DBF_FAST 2 @@ -408,16 +422,21 @@ private: * @param cumulative_difficulty the accumulated difficulty after this block * @param coins_generated the number of coins generated total after this block * @param blk_hash the hash of the block + * @param slippage_total the total value (expressed in FULM coins) of all slippage for this block + * @param yield_total the total of FULM coins that have been locked for yield in this block */ - virtual void add_block( const block& blk - , size_t block_weight - , uint64_t long_term_block_weight - , const difficulty_type& cumulative_difficulty - , const uint64_t& coins_generated - , uint64_t num_rct_outs - , oracle::asset_type_counts& cum_rct_by_asset_type - , const crypto::hash& blk_hash - ) = 0; + virtual void add_block( const block& blk, + size_t block_weight, + uint64_t long_term_block_weight, + const difficulty_type& cumulative_difficulty, + const uint64_t& coins_generated, + uint64_t num_rct_outs, + oracle::asset_type_counts& cum_rct_by_asset_type, + const crypto::hash& blk_hash, + uint64_t slippage_total, + uint64_t yield_total, + const cryptonote::network_type& nettype + ) = 0; /** * @brief remove data about the top block @@ -869,7 +888,8 @@ public: , const difficulty_type& cumulative_difficulty , const uint64_t& coins_generated , const std::vector>& txs - ); + , const cryptonote::network_type& nettype + ); /** * @brief checks if a block exists @@ -1898,6 +1918,10 @@ public: */ virtual uint64_t get_database_size() const = 0; + virtual int get_yield_block_info(const uint64_t height, yield_block_info& ybi) = 0; + virtual int get_yield_tx_info(const uint64_t height, std::vector& yti_container) = 0; + + /** * @brief set whether or not to automatically remove logs * diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 7636f9d29..c0a4dc276 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -212,8 +212,8 @@ namespace * * alt_blocks block hash {block data, block blob} * - * yield_block_data block height {} - * yield_tx_data block height {txn hash, dest address, amount} + * yield_block_data block height {slippage_coins, locked_coins, lc_total, network_health} + * yield_tx_data block height {txn hash, locked_coins, return_address} * * Note: where the data items are of uniform size, DUPFIXED tables have * been used to save space. In most of these cases, a dummy "zerokval" @@ -223,6 +223,7 @@ namespace * * The output_amounts table doesn't use a dummy key, but uses DUPSORT. */ + const char* const LMDB_BLOCKS = "blocks"; const char* const LMDB_BLOCK_HEIGHTS = "block_heights"; const char* const LMDB_BLOCK_INFO = "block_info"; @@ -258,17 +259,18 @@ const char* const LMDB_CIRC_SUPPLY_TALLY = "circ_supply_tally"; * * block_height (uint64_t) (this is the key field) * --------------------------------------------------------- - * txn_hash (crypto:hash) (so we can verify) - * dest_address (crypto::key) (where to send the yield) - * amount_locked (uint64_t) (how much was locked) + * txn_hash (crypto:hash) (so we can verify) + * dest_address (crypto::key) (where to send the yield) + * amount_locked (uint64_t) (how much was locked) * * We also have the following information that will go into a "yield_blocks" table: * - * block_height (uint64_t) (this is the key field) + * block_height (uint64_t) (this is the key field) * -------------------------------------------------------- - * slippage_amount (uint64_t) (amount needed to determine yield payout for the block) - * coins_locked (uint64_t) (total number of coins locked at this height) - * network_health (uint8_t) (a fudge factor used to adjust the slippage:yield ratio dynamically) + * slippage_amount (uint64_t) (amount needed to determine yield payout for the block) + * locked_coins (uint64_t) (total number of coins locked at this height) + * locked_coins_total (uint64_t) (total number of coins locked at this height) + * network_health (uint8_t) (a fudge factor used to adjust the slippage:yield ratio dynamically) * * So, let's say that we have a block height h for which we want to assess the yield payments. First off, * we are ONLY interested in making ANY payment if we have YIELD.block_height == h + 21600 (i.e. the yield @@ -375,33 +377,12 @@ typedef struct outassettype { uint64_t output_id; } outassettype; -typedef struct circ_supply { - crypto::hash tx_hash; - uint32_t asset_type; - uint64_t amount_burnt; - uint64_t amount_minted; -} circ_supply; - typedef struct circ_supply_tally { bool is_negative; uint64_t amount_hi; uint64_t amount_lo; } circ_supply_tally; -typedef struct yield_tx_data { - uint64_t block_height; - crypto::hash tx_hash; - crypto::public_key return_address; - uint64_t amount; -} yield_tx_data; - -typedef struct yield_block_data { - uint64_t block_height; - uint64_t slippage_total; - uint64_t locked_coins_total; - uint8_t network_health_percentage; -} yield_block_data; - std::atomic mdb_txn_safe::num_active_txns{0}; std::atomic_flag mdb_txn_safe::creation_gate = ATOMIC_FLAG_INIT; @@ -819,7 +800,66 @@ estim: return threshold_size; } -void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, uint64_t num_rct_outs, oracle::asset_type_counts& cum_rct_by_asset_type, const crypto::hash& blk_hash) +int BlockchainLMDB::get_yield_block_info(const uint64_t height, yield_block_info& ybi) +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + mdb_txn_cursors *m_cursors = &m_wcursors; + + // Clear the YBI, just in case + std::memset(&ybi, 0, sizeof(struct yield_block_info)); + + // Query for the matured YIELD_BLOCK_INFO information + CURSOR(yield_blocks) + MDB_val v; + MDB_val_set(k, height); + int ret = mdb_cursor_get(m_cur_yield_blocks, &k, &v, MDB_SET); + if (ret == MDB_NOTFOUND) { + LOG_ERROR("Failed to locate YBI for block height " << height); + return ret; + } + if (ret) + throw0(DB_ERROR(lmdb_error("Failed to enumerate yield block info: ", ret).c_str())); + + yield_block_info *p = (yield_block_info*)v.mv_data; + ybi = *p; + + // Return success to caller + return ret; +} + +int BlockchainLMDB::get_yield_tx_info(const uint64_t height, std::vector& yti_container) +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + mdb_txn_cursors *m_cursors = &m_wcursors; + + // Clear the container + yti_container.clear(); + + CURSOR(yield_txs) + MDB_val v; + MDB_val_set(k, height); + MDB_cursor_op op = MDB_FIRST_DUP; + while (1) + { + int ret = mdb_cursor_get(m_cur_yield_txs, &k, &v, op); + op = MDB_NEXT_DUP; + if (ret == MDB_NOTFOUND) + break; + if (ret) + throw0(DB_ERROR(lmdb_error("Failed to enumerate yield TX info: ", ret).c_str())); + + // Push result back into the container + yield_tx_info *p = (yield_tx_info*)v.mv_data; + yti_container.emplace_back(*p); + } + + // Return success to caller + return 0; +} + +void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, uint64_t num_rct_outs, oracle::asset_type_counts& cum_rct_by_asset_type, const crypto::hash& blk_hash, uint64_t slippage_total, uint64_t yield_total, const cryptonote::network_type& nettype) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -849,11 +889,50 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l int result = 0; + CURSOR(yield_blocks) + yield_block_info ybi_matured, ybi_prev, ybi; + uint64_t yield_lock_period = cryptonote::get_config(nettype).YIELD_LOCK_PERIOD; + if (m_height >= yield_lock_period) { + uint64_t height_matured = m_height - yield_lock_period - 1; + result = get_yield_block_info(height_matured, ybi_matured); + if (result) + { + throw0(DB_ERROR(lmdb_error("Failed to get YBI for matured height: ", result).c_str())); + } + } else { + // Chain is too new - just clear the memory of the "matured" YBI struct + std::memset(&ybi_matured, 0, sizeof(struct yield_block_info)); + ybi_prev.network_health_percentage = 100; + } + if (m_height >= 1) { + // Query for the latest YIELD_BLOCK_INFO information + result = get_yield_block_info(m_height - 1, ybi_prev); + if (result) + { + throw0(DB_ERROR(lmdb_error("Failed to get YBI for last block: ", result).c_str())); + } + } else { + // Chain is too new - just clear the memory of the "prev" YBI struct + std::memset(&ybi_prev, 0, sizeof(struct yield_block_info)); + ybi_prev.network_health_percentage = 100; + } + + // Create the YIELD_BLOCK_INFO instance for this block + ybi.block_height = m_height; + ybi.slippage_total = slippage_total; + ybi.locked_coins = yield_total; + ybi.locked_coins_tally = ybi_prev.locked_coins_tally - ybi_matured.locked_coins_tally + yield_total; + ybi.network_health_percentage = 100; + + // Put the YBI into the table MDB_val_set(key, m_height); + MDB_val_set(ybi_val, ybi); + result = mdb_cursor_put(m_cur_yield_blocks, &key, &ybi_val, MDB_APPEND); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to add YBI to db: ", result).c_str())); CURSOR(blocks) CURSOR(block_info) - CURSOR(circ_supply_tally) // this call to mdb_cursor_put will change height() cryptonote::blobdata block_blob(block_to_blob(blk)); @@ -1143,11 +1222,13 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons // Is there yield_tx data to add? if (tx.type == cryptonote::transaction_type::YIELD) { + // Create the object we are going to write to the database - yield_tx_data yield_data; + yield_tx_info yield_data; + yield_data.block_height = m_height; yield_data.tx_hash = tx_hash; yield_data.return_address = tx.return_address; - yield_data.amount = tx.amount_burnt; // SRCG - this feels as though we are bastardising the variable for an invalid purpose + yield_data.locked_coins = tx.amount_burnt; MDB_val_set(val_height, m_height); MDB_val_set(val_yield_tx_data, yield_data); result = mdb_cursor_put(m_cur_yield_txs, &val_height, &val_yield_tx_data, MDB_APPEND); @@ -1175,7 +1256,6 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const CURSOR(txs_prunable_hash) CURSOR(txs_prunable_tip) CURSOR(tx_outputs) - CURSOR(circ_supply) CURSOR(circ_supply_tally) CURSOR(yield_txs) @@ -1266,20 +1346,6 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const LOG_PRINT_L1("tx ID " << tip->data.tx_id << "\n\tAsset Type = " << cryptonote::asset_type_from_id(asset.first) << "\n\tTally before undoing mint =" << source_tally.str() << "\n\tTally after undoing mint =" << final_source_tally.str()); } } - /* - // Update the circ_supply table by deleting all entries for this TX - if ((result = mdb_cursor_get(m_cur_circ_supply, &val_tx_id, NULL, MDB_SET))) { - if (result == MDB_NOTFOUND) { - LOG_PRINT_L1("failed to obtain circulating supply data - no burns / conversions made yet?"); - } else { - throw1(DB_ERROR(lmdb_error("Failed to locate circulating supply for removal: ", result).c_str())); - } - } else { - result = mdb_cursor_del(m_cur_circ_supply, 0); - if (result) - throw1(DB_ERROR(lmdb_error("Failed to add removal of circulating supply to db transaction: ", result).c_str())); - } - */ remove_tx_outputs(tip->data.tx_id, tx); result = mdb_cursor_get(m_cur_tx_outputs, &val_tx_id, NULL, MDB_SET); @@ -1297,16 +1363,21 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const // Is there yield_tx data to remove? if (tx.type == cryptonote::transaction_type::YIELD) { // Remove any yield_tx data for this transaction - result = mdb_cursor_get(m_cur_yield_txs, &val_tx_id, NULL, MDB_SET); - if (result == MDB_NOTFOUND) - LOG_PRINT_L1("tx has no yield_tx data to remove: " << tx_hash); - else if (result) - throw1(DB_ERROR(lmdb_error("Failed to locate yield_tx data for removal: ", result).c_str())); - if (!result) - { - result = mdb_cursor_del(m_cur_yield_txs, 0); - if (result) - throw1(DB_ERROR(lmdb_error("Failed to add removal of yield_tx data to db transaction: ", result).c_str())); + MDB_val_set(val_height, m_height); + MDB_val v; + while (1) { + result = mdb_cursor_get(m_cur_yield_txs, &val_height, &v, MDB_SET); + if (result == MDB_NOTFOUND) + break; + else if (result) + throw1(DB_ERROR(lmdb_error("Failed to locate yield_tx data for removal: ", result).c_str())); + const yield_tx_info yti = *(const yield_tx_info*)v.mv_data; + if (yti.tx_hash == tx_hash) { + result = mdb_cursor_del(m_cur_yield_txs, 0); + if (result) + throw1(DB_ERROR(lmdb_error("Failed to add removal of yield_tx data to db transaction: ", result).c_str())); + break; + } } } @@ -1790,6 +1861,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) lmdb_db_open(txn, LMDB_CIRC_SUPPLY_TALLY, MDB_CREATE, m_circ_supply_tally, "Failed to open db handle for m_circ_supply_tally"); lmdb_db_open(txn, LMDB_YIELD_TXS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_yield_txs, "Failed to open db handle for m_yield_txs"); + lmdb_db_open(txn, LMDB_YIELD_BLOCKS, MDB_INTEGERKEY | MDB_CREATE, m_yield_blocks, "Failed to open db handle for m_yield_blocks"); mdb_set_dupsort(txn, m_spent_keys, compare_hash32); mdb_set_dupsort(txn, m_block_heights, compare_hash32); @@ -1993,6 +2065,8 @@ void BlockchainLMDB::reset() throw0(DB_ERROR(lmdb_error("Failed to drop m_properties: ", result).c_str())); if (auto result = mdb_drop(txn, m_yield_txs, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_yield_txs: ", result).c_str())); + if (auto result = mdb_drop(txn, m_yield_blocks, 0)) + throw0(DB_ERROR(lmdb_error("Failed to drop m_yield_blocks: ", result).c_str())); // init with current version MDB_val_str(k, "version"); @@ -4532,7 +4606,7 @@ void BlockchainLMDB::block_rtxn_abort() const } uint64_t BlockchainLMDB::add_block(const std::pair& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, - const std::vector>& txs) + const std::vector>& txs, const cryptonote::network_type& nettype) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -4550,7 +4624,7 @@ uint64_t BlockchainLMDB::add_block(const std::pair& blk, size_t try { - BlockchainDB::add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs); + BlockchainDB::add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs, nettype); } catch (const DB_ERROR_TXN_START& e) { diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 850f29f7c..dfb3aa9d2 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -341,6 +341,7 @@ public: , const difficulty_type& cumulative_difficulty , const uint64_t& coins_generated , const std::vector>& txs + , const cryptonote::network_type& nettype ); virtual void set_batch_transactions(bool batch_transactions); @@ -388,15 +389,18 @@ private: void check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes); uint64_t get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const; - virtual void add_block( const block& blk - , size_t block_weight - , uint64_t long_term_block_weight - , const difficulty_type& cumulative_difficulty - , const uint64_t& coins_generated - , uint64_t num_rct_outs - , oracle::asset_type_counts& cum_rct_by_asset_type - , const crypto::hash& block_hash - ); + virtual void add_block( const block& blk, + size_t block_weight, + uint64_t long_term_block_weight, + const difficulty_type& cumulative_difficulty, + const uint64_t& coins_generated, + uint64_t num_rct_outs, + oracle::asset_type_counts& cum_rct_by_asset_type, + const crypto::hash& blk_hash, + uint64_t slippage_total, + uint64_t yield_total, + const cryptonote::network_type& nettype + ); virtual void remove_block(); @@ -453,6 +457,9 @@ private: //void migrate_0_1(); void cleanup_batch(); + virtual int get_yield_block_info(const uint64_t height, yield_block_info& ybi); + virtual int get_yield_tx_info(const uint64_t height, std::vector& yti_container); + private: MDB_env* m_env; @@ -486,7 +493,9 @@ private: MDB_dbi m_circ_supply; MDB_dbi m_circ_supply_tally; + MDB_dbi m_yield_txs; + MDB_dbi m_yield_blocks; mutable uint64_t m_cum_size; // used in batch size estimation mutable unsigned int m_cum_count; diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 51ba88886..6b6903a5b 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -488,7 +488,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path try { uint64_t long_term_block_weight = core.get_blockchain_storage().get_next_long_term_block_weight(block_weight); - core.get_blockchain_storage().get_db().add_block(std::make_pair(b, block_to_blob(b)), block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs); + core.get_blockchain_storage().get_db().add_block(std::make_pair(b, block_to_blob(b)), block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, txs, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET); } catch (const std::exception& e) { diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat index eb3a40fa2..e69de29bb 100644 Binary files a/src/blocks/checkpoints.dat and b/src/blocks/checkpoints.dat differ diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index f887bd80f..68d33b575 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -218,11 +218,13 @@ namespace cryptonote FIELD(vout) FIELD(extra) VARINT_FIELD(type) - FIELD(return_address) - FIELD(source_asset_type) - FIELD(destination_asset_type) - VARINT_FIELD(amount_burnt) - VARINT_FIELD(amount_slippage_limit) + if (type != cryptonote::transaction_type::MINER && type != cryptonote::transaction_type::PROTOCOL) { + FIELD(return_address) + FIELD(source_asset_type) + FIELD(destination_asset_type) + VARINT_FIELD(amount_burnt) + VARINT_FIELD(amount_slippage_limit) + } END_SERIALIZE() public: diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index c6aa00c6d..663761b92 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -167,11 +167,13 @@ namespace boost a & x.vout; a & x.extra; a & x.type; - a & x.return_address; - a & x.source_asset_type; - a & x.destination_asset_type; - a & x.amount_burnt; - a & x.amount_slippage_limit; + if (x.type != cryptonote::transaction_type::MINER && x.type != cryptonote::transaction_type::PROTOCOL) { + a & x.return_address; + a & x.source_asset_type; + a & x.destination_asset_type; + a & x.amount_burnt; + a & x.amount_slippage_limit; + } } template @@ -183,20 +185,22 @@ namespace boost a & x.vout; a & x.extra; a & x.type; - a & x.return_address; - a & x.source_asset_type; - a & x.destination_asset_type; - a & x.amount_burnt; - a & x.amount_slippage_limit; - if (x.version == 1) - { - a & x.signatures; - } - else - { - a & (rct::rctSigBase&)x.rct_signatures; - if (x.rct_signatures.type != rct::RCTTypeNull) - a & x.rct_signatures.p; + if (x.type != cryptonote::transaction_type::MINER && x.type != cryptonote::transaction_type::PROTOCOL) { + a & x.return_address; + a & x.source_asset_type; + a & x.destination_asset_type; + a & x.amount_burnt; + a & x.amount_slippage_limit; + if (x.version == 1) + { + a & x.signatures; + } + else + { + a & (rct::rctSigBase&)x.rct_signatures; + if (x.rct_signatures.type != rct::RCTTypeNull) + a & x.rct_signatures.p; + } } } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index d490fab79..0cd8d9446 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -52,12 +52,11 @@ // MONEY_SUPPLY - total number coins to be generated #define MONEY_SUPPLY ((uint64_t)(1800000000000000ull)) -#define EMISSION_SPEED_FACTOR_PER_MINUTE (19) +#define EMISSION_SPEED_FACTOR_PER_MINUTE (20) #define FINAL_SUBSIDY_PER_MINUTE ((uint64_t)30000000) // 3 * pow(10, 7) #define BURN_LOCK_PERIOD 0 #define CONVERT_LOCK_PERIOD 0 -#define YIELD_LOCK_PERIOD 30*24*30 #define CRYPTONOTE_REWARD_BLOCKS_WINDOW 100 #define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 60000 //size of block (bytes) after which reward for block calculated using block size @@ -260,10 +259,12 @@ namespace config 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10 } }; // Bender's nightmare - std::string const GENESIS_TX = "020001ff000180c09e90acbb1402e8ee31433aa5ef7eb19af9740660381fe858b541d8c87e5ed38044d061c7072c0446554c4d3c0000000000000021019d1150e5f49422643dfeab470b89c6266f2b1b4469ab969f3bfba0bed74bcd1f0100000000000000000000000000000000000000000000000000000000000000000000000000"; - + std::string const GENESIS_TX = "020001ff000180c09e90acbb140228a98ddaae317689e1deb19444b8d60a5132a1f249d8d0ce72b6eba6b79c22240446554c4d3c000000000000002101d52228aa3413ee1bfe2b10fbc1a8cbe2ef9ab2cea0c6bd338103e5f7546384290100"; + uint32_t const GENESIS_NONCE = 10000; + const uint64_t YIELD_LOCK_PERIOD = 30*24*30; + // Hash domain separators const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; const char HASH_KEY_BULLETPROOF_PLUS_EXPONENT[] = "bulletproof_plus"; @@ -333,6 +334,8 @@ namespace config std::string const GENESIS_TX = "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; uint32_t const GENESIS_NONCE = 10001; + const uint64_t YIELD_LOCK_PERIOD = 20; + std::array const ORACLE_URLS = {{"oracle.fulmo.network:8443", "oracle.fulmo.network:8443", "oracle.fulmo.network:8443"}}; std::string const ORACLE_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" @@ -355,6 +358,8 @@ namespace config std::string const GENESIS_TX = "013c01ff0001ffffffffffff0302df5d56da0c7d643ddd1ce61901c7bdc5fb1738bfe39fbe69c28a3a7032729c0f2101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b"; uint32_t const GENESIS_NONCE = 10002; + const uint64_t YIELD_LOCK_PERIOD = 20; + std::array const ORACLE_URLS = {{"oracle.fulmo.network:8443", "oracle.fulmo.network:8443", "oracle.fulmo.network:8443"}}; std::string const ORACLE_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" @@ -387,6 +392,7 @@ namespace cryptonote uint32_t const GENESIS_NONCE; std::array const ORACLE_URLS; std::string const ORACLE_PUBLIC_KEY; + uint64_t YIELD_LOCK_PERIOD; }; inline const config_t& get_config(network_type nettype) { @@ -401,7 +407,8 @@ namespace cryptonote ::config::GENESIS_TX, ::config::GENESIS_NONCE, ::config::ORACLE_URLS, - ::config::ORACLE_PUBLIC_KEY + ::config::ORACLE_PUBLIC_KEY, + ::config::YIELD_LOCK_PERIOD }; static const config_t testnet = { ::config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, @@ -414,7 +421,8 @@ namespace cryptonote ::config::testnet::GENESIS_TX, ::config::testnet::GENESIS_NONCE, ::config::testnet::ORACLE_URLS, - ::config::testnet::ORACLE_PUBLIC_KEY + ::config::testnet::ORACLE_PUBLIC_KEY, + ::config::testnet::YIELD_LOCK_PERIOD }; static const config_t stagenet = { ::config::stagenet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, @@ -427,7 +435,8 @@ namespace cryptonote ::config::stagenet::GENESIS_TX, ::config::stagenet::GENESIS_NONCE, ::config::stagenet::ORACLE_URLS, - ::config::stagenet::ORACLE_PUBLIC_KEY + ::config::stagenet::ORACLE_PUBLIC_KEY, + ::config::stagenet::YIELD_LOCK_PERIOD }; switch (nettype) { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index d969233e1..f27b6c0e5 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -466,6 +466,18 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency()); } + // Preload the yield_block_info cache + uint64_t yield_lock_period = get_config(m_nettype).YIELD_LOCK_PERIOD; + m_yield_block_info_cache.clear(); + uint64_t end_height = m_db->height(); + uint64_t start_height = (end_height > yield_lock_period) ? (end_height - yield_lock_period) : 0; + for (uint64_t idx = start_height; idx < end_height; idx++) { + yield_block_info ybi; + int result = m_db->get_yield_block_info(idx, ybi); + if (result) + return false; + m_yield_block_info_cache[idx] = ybi; + } return true; } //------------------------------------------------------------------ @@ -1846,6 +1858,35 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, protocol_entries.push_back(entry); } + // Get the YIELD TX information for matured staked coins + uint64_t yield_lock_period = get_config(m_nettype).YIELD_LOCK_PERIOD; + uint64_t start_height = height - yield_lock_period - 1; + std::vector yield_entries; + int yield_tx_result = m_db->get_yield_tx_info(start_height, yield_entries); + if (yield_entries.size()) { + + // Get the YBI information for the 21,600 blocks that the matured TX(s), we can calculate yield + std::vector> yield_payouts; + for (const auto& entry: yield_entries) { + yield_payouts.emplace_back(std::make_pair(entry, 0)); + } + + // Make sure the cache is fully populated and up to date + if (!validate_ybi_cache()) { + LOG_PRINT_L1("yield information cache is invalid - rebuilding cache"); + if (!rebuild_ybi_cache()) { + LOG_ERROR("Failed to rebuild yield information cache - aborting"); + return false; + } + } + + // Iterate over the cached data for block yield, calculating the yield payouts due + if (!calculate_yield_payouts(start_height, yield_payouts)) { + LOG_ERROR("Failed to obtain yield payout information - aborting"); + return false; + } + } + // Time to construct the protocol_tx uint64_t protocol_fee = 0; bool ok = construct_protocol_tx(height, protocol_fee, b.protocol_tx, protocol_entries, circ_supply, pr, b.major_version); @@ -4226,6 +4267,104 @@ uint64_t Blockchain::get_adjusted_time(uint64_t height) const // we do this since it's better to report a time in the past than a time in the future return (adjusted_current_block_ts < median_ts ? adjusted_current_block_ts : median_ts); } +//------------------------------------------------------------------ +bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vector>& yield_container) +{ + LOG_PRINT_L3("Blockchain::" << __func__); + + // Iterate over the cached yield_block_info data + uint64_t yield_lock_period = cryptonote::get_config(m_nettype).YIELD_LOCK_PERIOD; + for (uint64_t idx = start_height; idx < start_height + yield_lock_period; ++idx) { + // Get the next block + if (m_yield_block_info_cache.count(idx) == 0) { + LOG_ERROR("failed to locate yield information for block height " << idx <<" - aborting"); + return false; + } + yield_block_info ybi = m_yield_block_info_cache[idx]; + boost::multiprecision::int128_t slippage_128 = ybi.slippage_total; + slippage_128 = (slippage_128 * 3) / 10; + + // Get the total number of coins locked at this height + boost::multiprecision::int128_t locked_total_128 = ybi.locked_coins_tally; + + // Iterate over the yield_container, adding each proportion of the yield + for (const auto& entry: yield_container) { + + boost::multiprecision::int128_t locked_coins_128 = entry.first.locked_coins; + boost::multiprecision::int128_t yield_128 = (slippage_128 * locked_coins_128) / locked_total_128; + } + + } + + // Return success to caller + return true; +} +//------------------------------------------------------------------ +bool Blockchain::rebuild_ybi_cache() +{ + LOG_PRINT_L3("Blockchain::" << __func__); + + // If we need to (re)build the cache, we need to pull the data from the blockchain directly + + // Clear the existing cache + m_yield_block_info_cache.clear(); + + // Get the size that the cache should be when fully populated (could be less than the lock period if the chain is young) + uint64_t height = m_db->height(); + uint64_t yield_lock_period = cryptonote::get_config(m_nettype).YIELD_LOCK_PERIOD; + uint64_t ybi_cache_expected_size = std::min(height, yield_lock_period); + + // Now get this number of entries from the blockchain + for (uint64_t idx = height - ybi_cache_expected_size; idx < height; ++idx) { + + // Get the specified YBI entry + yield_block_info ybi; + int result = m_db->get_yield_block_info(idx, ybi); + if (result) { + // Request failed - report error and bail out + LOG_ERROR("failed to retrieve YBI entry for height " << idx << " - aborting"); + return false; + } + + // Store in the map + m_yield_block_info_cache[idx] = ybi; + } + + // Return success to caller + return true; +} +//------------------------------------------------------------------ +bool Blockchain::validate_ybi_cache() +{ + LOG_PRINT_L3("Blockchain::" << __func__); + + // Get the size that the cache should be if fully populated + uint64_t height = m_db->height(); + uint64_t yield_lock_period = cryptonote::get_config(m_nettype).YIELD_LOCK_PERIOD; + uint64_t ybi_cache_expected_size = std::min(height, yield_lock_period); + if (m_yield_block_info_cache.size() != ybi_cache_expected_size) { + // It's not the right size - report error and bail out + LOG_ERROR("YBI cache is incorrect size - should be " << ybi_cache_expected_size << ", but found " << m_yield_block_info_cache.size() << " - aborting"); + return false; + } + + // It's the right size - check we have the correct limits + if (m_yield_block_info_cache.count(height - 1) == 0) { + // Missing the latest block - report error and bail out + LOG_ERROR("Failed to locate YBI entry for height " << (height - 1) << " - aborting"); + return false; + } + + if (m_yield_block_info_cache.count(height - ybi_cache_expected_size - 1) == 0) { + // Missing the latest block - report error and bail out + LOG_ERROR("Failed to locate YBI entry for height " << (height - ybi_cache_expected_size - 1) << " - aborting"); + return false; + } + + return true; +} + + //------------------------------------------------------------------ //TODO: revisit, has changed a bit on upstream bool Blockchain::check_block_timestamp(std::vector& timestamps, const block& b, uint64_t& median_ts) const @@ -4675,7 +4814,7 @@ leave: { uint64_t long_term_block_weight = get_next_long_term_block_weight(block_weight); cryptonote::blobdata bd = cryptonote::block_to_blob(bl); - new_height = m_db->add_block(std::make_pair(std::move(bl), std::move(bd)), block_weight, long_term_block_weight, cumulative_difficulty, already_generated_coins, txs); + new_height = m_db->add_block(std::make_pair(std::move(bl), std::move(bd)), block_weight, long_term_block_weight, cumulative_difficulty, already_generated_coins, txs, m_nettype); } catch (const KEY_IMAGE_EXISTS& e) { diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 0472e407d..0c1394114 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -746,6 +746,8 @@ namespace cryptonote */ uint64_t get_current_cumulative_block_weight_median() const; + int get_yield_info(const uint64_t start_height, const uint64_t end_height, std::vector>& yield_container); + /** * @brief gets the difficulty of the block with a given height * @@ -1145,6 +1147,33 @@ namespace cryptonote */ uint64_t get_adjusted_time(uint64_t height) const; + /** + * calculate the yield payouts + * + * @return TRUE if the payouts were calculated successfully, FALSE otherwise + */ + bool calculate_yield_payouts(const uint64_t start_height, std::vector>& yield_payouts); + + /** + * (re)build the yield_block_info cache from the blockchain + * + * @return TRUE if the cache rebuilt correctly, FALSE otherwise + */ + bool rebuild_ybi_cache(); + + /** + * @brief validate the yield_block_info cache + * + * Checks that the m_yield_block_info_cache is fully populated by + * checking the size of the map, and making sure it has the most recent entry + * and the oldest expected entry as well + * + * Returns TRUE if the cache is intact, full, and up-to-date, FALSE otherwise + * + * @return TRUE if cache is OK, FALSE otherwise + */ + bool validate_ybi_cache(); + #ifndef IN_UNIT_TESTS private: #endif @@ -1250,6 +1279,8 @@ namespace cryptonote // cache for verifying transaction RCT non semantics mutable rct_ver_cache_t m_rct_ver_cache; + std::map m_yield_block_info_cache; + /** * @brief collects the keys for all outputs being "spent" as an input * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 7183b32d7..9e1b97e7a 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -925,7 +925,12 @@ namespace cryptonote tx_info[n].result = false; break; case rct::RCTTypeSimple: - if (!rct::verRctSemanticsSimple(rv, tx_info[n].tx->amount_burnt)) + if (!rct::verRctSemanticsSimple(rv, + tx_info[n].tx->type == cryptonote::transaction_type::BURN ? tx_info[n].tx->amount_burnt : + tx_info[n].tx->type == cryptonote::transaction_type::CONVERT ? tx_info[n].tx->amount_burnt : + tx_info[n].tx->type == cryptonote::transaction_type::YIELD ? tx_info[n].tx->amount_burnt : + 0 + )) { MERROR_VER("rct signature semantics check failed"); set_semantics_failed(tx_info[n].tx_hash); @@ -978,7 +983,7 @@ namespace cryptonote } if (!rvv.empty()) { - LOG_PRINT_L1("One transaction among this group has bad semantics, verifying one at a time"); + LOG_PRINT_L1("Verifying one TX at a time"); ret = false; for (size_t n = 0; n < tx_info.size(); ++n) { @@ -986,7 +991,12 @@ namespace cryptonote continue; if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus) continue; - if (!rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures, tx_info[n].tx->amount_burnt)) + if (!rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures, + tx_info[n].tx->type == cryptonote::transaction_type::BURN ? tx_info[n].tx->amount_burnt : + tx_info[n].tx->type == cryptonote::transaction_type::CONVERT ? tx_info[n].tx->amount_burnt : + tx_info[n].tx->type == cryptonote::transaction_type::YIELD ? tx_info[n].tx->amount_burnt : + 0 + )) { set_semantics_failed(tx_info[n].tx_hash); tx_info[n].tvc.m_verifivation_failed = true; diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 4fc168d14..5aeea0599 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -553,12 +553,6 @@ namespace cryptonote assert(false); } - /* - // Print out the uniqueness - crypto::public_key pk_uniq; - std::memcpy(pk_uniq.data, uniqueness.data, sizeof(crypto::public_key)); - LOG_ERROR("*** UNIQUENESS : " << pk_uniq); - */ return true; } //--------------------------------------------------------------- @@ -907,6 +901,12 @@ namespace cryptonote tx.amount_burnt += dst_entr.amount; continue; } + } else if (tx_type == cryptonote::transaction_type::YIELD) { + // Do not create outputs that are staked for yield - discard them as unused + if (!dst_entr.is_change) { + tx.amount_burnt += dst_entr.amount; + continue; + } } // Get the uniqueness for this TX @@ -941,10 +941,29 @@ namespace cryptonote CHECK_AND_ASSERT_MES(calculate_uniqueness(tx.type, k_image, 0, 0, uniqueness), false, "Failed to calculate uniqueness for the transaction"); // Get the output public key for the change output - crypto::public_key P_change; + crypto::public_key P_change = crypto::null_pkey; CHECK_AND_ASSERT_MES(tx.vout.size() == 1, false, "Internal error - too many outputs for CONVERT tx"); CHECK_AND_ASSERT_MES(cryptonote::get_output_public_key(tx.vout[0], P_change), false, "Internal error - failed to get TX change output public key"); + CHECK_AND_ASSERT_MES(P_change != crypto::null_pkey, false, "Internal error - not found TX change output for CONVERT tx"); + // Now generate the return address + CHECK_AND_ASSERT_MES(get_return_address(tx.version, uniqueness, sender_account_keys, P_change, txkey_pub, tx.return_address, hwdev), false, "Failed to get protocol destination address"); + + } else if (tx_type == cryptonote::transaction_type::YIELD) { + + // Get the uniqueness for this TX - must be output zero we are interested in for a CONVERT or YIELD TX + CHECK_AND_ASSERT_MES(!tx.vin.empty(), false, "tx.vin[] is empty"); + CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), false, "incorrect tx.vin[0] type for YIELD TX"); + crypto::key_image k_image = boost::get(tx.vin[0]).k_image; + ec_scalar uniqueness; + CHECK_AND_ASSERT_MES(calculate_uniqueness(tx.type, k_image, 0, 0, uniqueness), false, "Failed to calculate uniqueness for the transaction"); + + // Get the output public key for the change output + crypto::public_key P_change = crypto::null_pkey; + CHECK_AND_ASSERT_MES(tx.vout.size() == 1, false, "Internal error - incorrect number of outputs for YIELD tx"); + CHECK_AND_ASSERT_MES(cryptonote::get_output_public_key(tx.vout[0], P_change), false, "Internal error - failed to get TX change output public key"); + CHECK_AND_ASSERT_MES(P_change != crypto::null_pkey, false, "Internal error - not found TX change output for YIELD tx"); + // Now generate the return address CHECK_AND_ASSERT_MES(get_return_address(tx.version, uniqueness, sender_account_keys, P_change, txkey_pub, tx.return_address, hwdev), false, "Failed to get protocol destination address"); } @@ -1125,9 +1144,29 @@ namespace cryptonote if (sources[i].rct) boost::get(tx.vin[i]).amount = 0; } - for (size_t i = 0; i < tx.vout.size(); ++i) - tx.vout[i].amount = 0; + std::vector zero_masks; + zero_masks.reserve(tx.vout.size()); + for (size_t i = 0; i < tx.vout.size(); ++i) { + if (tx.type == cryptonote::transaction_type::YIELD) { + uint64_t unlock_time = 0; + bool ok = get_output_unlock_time(tx.vout[i], unlock_time); + if (!ok) { + LOG_ERROR("failed to get output asset type for tx.vout[" << i << "]"); + return false; + } + if (unlock_time == 0) { + zero_masks.emplace_back(false); + } else { + zero_masks.emplace_back(true); + } + } else { + zero_masks.emplace_back(false); + } + // Clear the amount in the output + tx.vout[i].amount = 0; + } + crypto::hash tx_prefix_hash; get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev); rct::ctkeyV outSk; @@ -1139,6 +1178,7 @@ namespace cryptonote tx_type, source_asset, destination_asset_types, + zero_masks, inamounts, outamounts, fee, diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index a656f388f..7e741f867 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -121,12 +121,18 @@ namespace namespace rct { - Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector &amounts, epee::span sk, hw::device &hwdev) + Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector &zero_masks, const std::vector &amounts, epee::span sk, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); + CHECK_AND_ASSERT_THROW_MES(amounts.size() == zero_masks.size(), "Invalid amounts/zero_masks sizes"); masks.resize(amounts.size()); - for (size_t i = 0; i < masks.size(); ++i) - masks[i] = hwdev.genCommitmentMask(sk[i]); + for (size_t i = 0; i < masks.size(); ++i) { + if (zero_masks[i] == true) { + masks[i] = rct::identity(); + } else { + masks[i] = hwdev.genCommitmentMask(sk[i]); + } + } Bulletproof proof = bulletproof_PROVE(amounts, masks); CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); C = proof.V; @@ -147,12 +153,18 @@ namespace rct { catch (...) { return false; } } - BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector &amounts, epee::span sk, hw::device &hwdev) + BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector &zero_masks, const std::vector &amounts, epee::span sk, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); + CHECK_AND_ASSERT_THROW_MES(amounts.size() == zero_masks.size(), "Invalid amounts/zero_masks sizes"); masks.resize(amounts.size()); - for (size_t i = 0; i < masks.size(); ++i) - masks[i] = hwdev.genCommitmentMask(sk[i]); + for (size_t i = 0; i < masks.size(); ++i) { + if (zero_masks[i] == true) { + masks[i] = rct::identity(); + } else { + masks[i] = hwdev.genCommitmentMask(sk[i]); + } + } BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks); CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); C = proof.V; @@ -1110,8 +1122,9 @@ namespace rct { const cryptonote::transaction_type tx_type, const std::string& in_asset_type, const std::vector & destination_asset_types, - const vector &inamounts, - const vector &outamounts, + const std::vector &zero_masks, + const std::vector &inamounts, + const std::vector &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, @@ -1126,6 +1139,7 @@ namespace rct { CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); + CHECK_AND_ASSERT_THROW_MES(zero_masks.size() == destinations.size(), "Different number of zero_masks/destinations"); CHECK_AND_ASSERT_THROW_MES(index.size() == inSk.size(), "Different number of index/inSk"); CHECK_AND_ASSERT_THROW_MES(mixRing.size() == inSk.size(), "Different number of mixRing/inSk"); for (size_t n = 0; n < mixRing.size(); ++n) { @@ -1192,9 +1206,9 @@ namespace rct { { const epee::span keys{&amount_keys[0], amount_keys.size()}; if (plus) - rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev)); + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, zero_masks, outamounts, keys, hwdev)); else - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, zero_masks, outamounts, keys, hwdev)); #ifdef DBG if (plus) CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); @@ -1230,9 +1244,9 @@ namespace rct { { const epee::span keys{&amount_keys[amounts_proved], batch_size}; if (plus) - rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev)); + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, zero_masks, batch_amounts, keys, hwdev)); else - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, zero_masks, batch_amounts, keys, hwdev)); #ifdef DBG if (plus) CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); @@ -1308,9 +1322,10 @@ namespace rct { const keyV & destinations, const cryptonote::transaction_type tx_type, const std::string& in_asset_type, - const std::vector & destination_asset_types, - const vector &inamounts, - const vector &outamounts, + const std::vector & destination_asset_types, + const std::vector &zero_masks, + const std::vector &inamounts, + const std::vector &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, @@ -1326,7 +1341,7 @@ namespace rct { mixRing[i].resize(mixin+1); index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); } - return genRctSimple(message, inSk, destinations, tx_type, in_asset_type, destination_asset_types, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, rct_config, hwdev); + return genRctSimple(message, inSk, destinations, tx_type, in_asset_type, destination_asset_types, zero_masks, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RingCT protocol diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 34cbe3698..38e1d64bc 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -135,6 +135,7 @@ namespace rct { const cryptonote::transaction_type tx_type, const std::string& in_asset_type, const std::vector & destination_asset_types, + const std::vector &zero_masks, const std::vector & inamounts, const std::vector & outamounts, const keyV &amount_keys, @@ -150,6 +151,7 @@ namespace rct { const cryptonote::transaction_type tx_type, const std::string& in_asset_type, const std::vector & destination_asset_types, + const std::vector &zero_masks, const std::vector & inamounts, const std::vector & outamounts, xmr_amount txnFee, diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index dbfbce050..5c6f0f6c5 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6827,17 +6827,19 @@ bool simple_wallet::transfer_main( ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::CONVERT, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); break; case LockForYield: - unlock_block = YIELD_LOCK_PERIOD; + unlock_block = get_config(m_wallet->nettype()).YIELD_LOCK_PERIOD; ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::YIELD, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); break; case TransferLocked: + /* bc_height = get_daemon_blockchain_height(err); if (!err.empty()) { fail_msg_writer() << tr("failed to get blockchain height: ") << err; return false; } - unlock_block = bc_height + locked_blocks; + */ + unlock_block = locked_blocks; ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::TRANSFER, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); break; default: diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 90fca575a..17c469797 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2044,11 +2044,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote std::string source_asset = tx.source_asset_type; std::string dest_asset = tx.destination_asset_type; - cryptonote::transaction_type tx_type_verify = tx.type; - if (!miner_tx) { - THROW_WALLET_EXCEPTION_IF(!cryptonote::get_tx_type(source_asset, dest_asset, tx_type_verify), error::wallet_internal_error, "Failed to get TX type"); - THROW_WALLET_EXCEPTION_IF(tx_type_verify != tx.type, error::wallet_internal_error, "Incorrect TX type"); - } // per receiving subaddress index std::unordered_map> tx_money_got_in_outs; @@ -2257,20 +2252,20 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote //usually we have only one transfer for user in transaction if (!pool) { - THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size() || tx.vout.size() != asset_type_output_indices.size(), error::wallet_internal_error, - "transactions outputs size=" + std::to_string(tx.vout.size()) + - " not match with daemon response size=" + std::to_string(o_indices.size()) - + " or with asset outputs size=" + std::to_string(asset_type_output_indices.size())); + THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size() || tx.vout.size() != asset_type_output_indices.size(), error::wallet_internal_error, + "transactions outputs size=" + std::to_string(tx.vout.size()) + + " not match with daemon response size=" + std::to_string(o_indices.size()) + + " or with asset outputs size=" + std::to_string(asset_type_output_indices.size())); } for(size_t o: outs) { - THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + - std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); + THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); auto kit = m_pub_keys.find(tx_scan_info[o].in_ephemeral.pub); - THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(), + THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(), error::wallet_internal_error, std::string("Unexpected transfer index from public key: ") + "got " + (kit == m_pub_keys.end() ? "" : boost::lexical_cast(kit->second)) + ", m_transfers.size() is " + boost::lexical_cast(m_transfers.size())); @@ -2337,10 +2332,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_rct = false; } td.m_frozen = false; - set_unspent(m_transfers.size()-1); + set_unspent(m_transfers.size()-1); if (td.m_key_image_known) - m_key_images[td.m_key_image] = m_transfers.size()-1; - m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1; + m_key_images[td.m_key_image] = m_transfers.size()-1; + m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1; if (output_tracker_cache) (*output_tracker_cache)[std::make_pair(tx.vout[o].amount, td.m_global_output_index)] = m_transfers.size() - 1; if (m_multisig) @@ -2366,14 +2361,21 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // Add the change output_public_key to the list of subaddresses to check crypto::public_key P_change = crypto::null_pkey; THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_public_key(tx.vout[0], P_change), error::wallet_internal_error, "Failed to get change output public key"); - //m_subaddresses[P_change] = {0x50524F54,0x4F434F4C}; + //m_subaddresses[P_change] = {0x50524F54,0x4F434F4C}; /* {PROT,OCOL} - seemed like a good idea at the time, but harder to implement! */ m_subaddresses[P_change] = {0,0}; m_protocol_txs.insert({P_change, m_transfers.size()-1}); + + if (tx.type == cryptonote::transaction_type::YIELD) { + // Additionally, with YIELD TXs, we need to update our "balance staked" subtotal, because otherwise our balance is out by the staked coins until they mature! + // SRCG: must remember to deduct the number of staked coins when they mature!! + LOG_ERROR("***** STAKED COINS : " << tx.amount_burnt << " *****"); + m_locked_coins.insert({P_change, {0, tx.amount_burnt}}); + } } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx_scan_info[o].amount) { - LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) + LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx_scan_info[o].amount) << " output already exists with " << (m_transfers[kit->second].m_spent ? "spent" : "unspent") << " " << print_money(m_transfers[kit->second].amount()) << " in tx " << m_transfers[kit->second].m_txid << ", received output ignored"); @@ -2389,7 +2391,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } else { - LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) + LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx_scan_info[o].amount) << " output already exists with " << print_money(m_transfers[kit->second].amount()) << ", replacing with new output"); // The new larger output replaced a previous smaller one @@ -4031,6 +4033,7 @@ bool wallet2::clear() m_blockchain.clear(); m_transfers.clear(); m_transfers_indices.clear(); + m_locked_coins.clear(); m_key_images.clear(); m_pub_keys.clear(); m_unconfirmed_txs.clear(); @@ -4054,6 +4057,7 @@ void wallet2::clear_soft(bool keep_key_images) m_blockchain.clear(); m_transfers.clear(); m_transfers_indices.clear(); + m_locked_coins.clear(); if (!keep_key_images) m_key_images.clear(); m_pub_keys.clear(); @@ -6163,6 +6167,11 @@ uint64_t wallet2::balance(uint32_t index_major, const std::string& asset_type, b uint64_t amount = 0; for (const auto& i : balance_per_subaddress(index_major, asset_type, strict)) amount += i.second; + if (asset_type == "FULM") { + // Iterate over the locked coins, adding them to the _locked_ balance + for (const auto& i : m_locked_coins) + amount += i.second.m_amount; + } return amount; } //---------------------------------------------------------------------------------------------------- @@ -9329,6 +9338,7 @@ void wallet2::transfer_selected_rct(std::vector splitted_dsts = dsts; + cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); change_dts.amount = found_money - needed_money; change_dts.asset_type = source_asset; @@ -10351,7 +10361,12 @@ bool wallet2::sanity_check(const std::vector &ptx_vector, s check_tx_proof(ptx.tx, address, r.second.second, "automatic-sanity-check", proof, received); } catch (const std::exception &e) { received = 0; } - received += ptx.tx.amount_burnt; + + if (ptx.tx.type == cryptonote::transaction_type::CONVERT) + received += ptx.tx.amount_burnt; + else if (ptx.tx.type == cryptonote::transaction_type::YIELD) + received += ptx.tx.amount_burnt; + total_received += received; } @@ -11415,7 +11430,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt k_image = boost::get(tx.vin[0]).k_image; } crypto::ec_scalar uniqueness; - THROW_WALLET_EXCEPTION_IF(!cryptonote::calculate_uniqueness(tx.type, k_image, ((size_t)(-1)), 0, uniqueness), error::wallet_internal_error, "Failed to calculate uniqueness"); + THROW_WALLET_EXCEPTION_IF(!cryptonote::calculate_uniqueness(tx.type, k_image, ((size_t)(-1)), n, uniqueness), error::wallet_internal_error, "Failed to calculate uniqueness"); crypto::key_derivation found_derivation; if (is_out_to_acc(address, output_public_key, derivation, additional_derivations, n, uniqueness, get_output_view_tag(tx.vout[n]), found_derivation)) @@ -11431,7 +11446,13 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt //crypto::hash uniqueness = cn_fast_hash(reinterpret_cast(&n), sizeof(size_t)); crypto::derivation_to_scalar(found_derivation, uniqueness, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + if (tx.type == cryptonote::transaction_type::YIELD) { + uint64_t unlock_time = 0; + THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_unlock_time(tx.vout[n], unlock_time), error::wallet_internal_error, "Failed to get output unlock time"); + if (unlock_time == get_config(m_nettype).YIELD_LOCK_PERIOD) + ecdh_info.mask = rct::identity(); + } const rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d66f00d5e..a006291fe 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -471,6 +471,17 @@ private: END_SERIALIZE() }; + struct locked_yield_details + { + uint32_t m_index_major; + uint64_t m_amount; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(m_index_major) + VARINT_FIELD(m_amount) + END_SERIALIZE() + }; + struct unconfirmed_transfer_details { cryptonote::transaction_prefix m_tx; @@ -1155,6 +1166,7 @@ private: } a & m_transfers; a & m_transfers_indices; + a & m_locked_coins; a & m_account_public_address; a & m_key_images.parent(); if(ver < 6) @@ -1266,6 +1278,7 @@ private: FIELD(m_blockchain) FIELD(m_transfers) FIELD(m_transfers_indices) + FIELD(m_locked_coins) FIELD(m_account_public_address) FIELD(m_key_images) FIELD(m_unconfirmed_txs) @@ -1807,6 +1820,7 @@ private: transfer_container m_transfers; transfer_details_indices m_transfers_indices; + serializable_unordered_map m_locked_coins; payment_container m_payments; serializable_unordered_map m_key_images; serializable_unordered_map m_pub_keys; @@ -2127,6 +2141,13 @@ namespace boost a & x.m_signers; } + template + inline void serialize(Archive &a, tools::wallet2::locked_yield_details &x, const boost::serialization::version_type ver) + { + a & x.m_index_major; + a & x.m_amount; + } + template inline void serialize(Archive &a, tools::wallet2::unconfirmed_transfer_details &x, const boost::serialization::version_type ver) {