Added functions to cache and manage yield calculations

Removed "tx.amount_locked" field - "tx.amount_burnt" is technically correct for all cases.
Removed invalid checkpoint data.
This commit is contained in:
Some Random Crypto Guy
2024-01-30 14:22:46 +00:00
parent a0d2044b5d
commit a3a7f686f3
18 changed files with 605 additions and 177 deletions
+140 -1
View File
@@ -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<cryptonote::yield_tx_info> 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<std::pair<yield_tx_info, uint64_t>> 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<std::pair<yield_tx_info, uint64_t>>& 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<uint64_t>& 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)
{
+31
View File
@@ -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<std::pair<yield_tx_info, uint64_t>>& 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<std::pair<yield_tx_info, uint64_t>>& 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<uint64_t, yield_block_info> m_yield_block_info_cache;
/**
* @brief collects the keys for all outputs being "spent" as an input
*
+13 -3
View File
@@ -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;
+49 -9
View File
@@ -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<cryptonote::txin_to_key>(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<txin_to_key>(tx.vin[i]).amount = 0;
}
for (size_t i = 0; i < tx.vout.size(); ++i)
tx.vout[i].amount = 0;
std::vector<bool> 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,