From 041cd03098a63d16e46130fe0fdff2512411991a Mon Sep 17 00:00:00 2001 From: auruya Date: Wed, 3 Sep 2025 18:00:34 +0300 Subject: [PATCH] Add hardfork for stake carrot integration (#55) * Add hardfork for stake carrot integration * Add hardfork for stake carrot integration * fixed assertion failure * Add hardfork for stake carrot integration * Add hardfork for stake carrot integration --------- Co-authored-by: Some Random Crypto Guy --- .../cryptonote_format_utils.cpp | 20 ++++++-- src/cryptonote_config.h | 1 + src/cryptonote_core/blockchain.cpp | 42 +++++++++++------ src/cryptonote_core/cryptonote_tx_utils.cpp | 46 +++++++++++++++---- src/cryptonote_core/cryptonote_tx_utils.h | 1 + src/hardforks/hardforks.cpp | 3 ++ 6 files changed, 89 insertions(+), 24 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 1a5e9ef4e..32018c27c 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1381,17 +1381,31 @@ namespace cryptonote for (const auto &o: tx.vout) { - if (hf_version >= HF_VERSION_CARROT) + if (hf_version >= HF_VERSION_ENFORCE_CARROT) { // from v10, require outputs be carrot outputs CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_carrot_v1), false, "wrong variant type: " << o.target.type().name() << ", expected txout_to_carrot_v1 in transaction id=" << get_transaction_hash(tx)); - } else { + } else if (hf_version >= HF_VERSION_CARROT) { + if (tx.type != cryptonote::transaction_type::PROTOCOL) { + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_carrot_v1), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_carrot_v1 in transaction id=" << get_transaction_hash(tx)); + } else { + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key) || + o.target.type() == typeid(txout_to_tagged_key) || + o.target.type() == typeid(txout_to_carrot_v1), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_key or txout_to_tagged_key or txout_to_carrot_v1 in protocol transaction"); + // require all outputs in a tx be of the same type + CHECK_AND_ASSERT_MES(o.target.type() == tx.vout[0].target.type(), false, "non-matching variant types: " + << o.target.type().name() << " and " << tx.vout[0].target.type().name() << ", " + << "expected matching variant types in protocol transaction"); + } + } + else { // require outputs be of type txout_to_key OR txout_to_tagged_key // to allow grace period before requiring all to be txout_to_tagged_key CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key) || o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: " << o.target.type().name() << ", expected txout_to_key or txout_to_tagged_key in transaction"); - // require all outputs in a tx be of the same type CHECK_AND_ASSERT_MES(o.target.type() == tx.vout[0].target.type(), false, "non-matching variant types: " << o.target.type().name() << " and " << tx.vout[0].target.type().name() << ", " diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 74cfd456a..9ba859ea2 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -244,6 +244,7 @@ #define HF_VERSION_AUDIT2 8 #define HF_VERSION_AUDIT2_PAUSE 9 #define HF_VERSION_CARROT 10 +#define HF_VERSION_ENFORCE_CARROT 11 #define HF_VERSION_REQUIRE_VIEW_TAGS 255 #define HF_VERSION_ENABLE_CONVERT 255 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 47b97fa6b..d16950de2 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1404,6 +1404,11 @@ bool Blockchain::prevalidate_protocol_transaction(const block& b, uint64_t heigh CHECK_AND_ASSERT_MES(b.protocol_tx.version > 1, false, "Invalid coinbase protocol transaction version"); if (hf_version >= HF_VERSION_CARROT) { + CHECK_AND_ASSERT_MES(b.protocol_tx.version == TRANSACTION_VERSION_CARROT || 2, false, "protocol transaction has wrong version"); + CHECK_AND_ASSERT_MES(b.protocol_tx.type == cryptonote::transaction_type::PROTOCOL, false, "protocol transaction has wrong type"); + } + + if (hf_version >= HF_VERSION_ENFORCE_CARROT) { CHECK_AND_ASSERT_MES(b.protocol_tx.version == TRANSACTION_VERSION_CARROT, false, "protocol transaction has wrong version"); CHECK_AND_ASSERT_MES(b.protocol_tx.type == cryptonote::transaction_type::PROTOCOL, false, "protocol transaction has wrong type"); } @@ -1516,6 +1521,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl case HF_VERSION_AUDIT2: case HF_VERSION_AUDIT2_PAUSE: case HF_VERSION_CARROT: + case HF_VERSION_ENFORCE_CARROT: if (b.miner_tx.amount_burnt > 0) { CHECK_AND_ASSERT_MES(money_in_use + b.miner_tx.amount_burnt > money_in_use, false, "miner transaction is overflowed by amount_burnt"); money_in_use += b.miner_tx.amount_burnt; @@ -1583,7 +1589,7 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, LOG_PRINT_L1("Block at height: " << height << " - no yield payouts due - skipping"); } else { // Iterate over the cached data for block yield, calculating the yield payouts due - if (hf_version >= HF_VERSION_CARROT) { + if (get_ideal_hard_fork_version(matured_height) >= HF_VERSION_CARROT) { if (!calculate_yield_payouts(matured_height, carrot_yield_payouts)) { LOG_ERROR("Block at height: " << height << " - Failed to obtain carrot yield payout information - aborting"); return false; @@ -1981,11 +1987,14 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, cryptonote::yield_block_info ybi_matured; bool ok = get_ybi_entry(start_height, ybi_matured); if (ok && ybi_matured.locked_coins_this_block > 0) { - + + // Work out what the asset_type should be based on height of submission + uint8_t hf_submitted = m_hardfork->get_ideal_version(start_height); + // Iterate over the cached data for block yield, calculating the yield payouts due std::vector> yield_payouts; - std::vector> carrot_yield_payouts; - if (b.major_version >= HF_VERSION_CARROT) { + std::vector> carrot_yield_payouts; + if (hf_submitted >= HF_VERSION_CARROT) { if (!calculate_yield_payouts(start_height, carrot_yield_payouts)) { LOG_ERROR("Failed to obtain yield payout information - aborting"); return false; @@ -1997,11 +2006,8 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, } } - // Work out what the asset_type should be based on height of submission - uint8_t hf_submitted = m_hardfork->get_ideal_version(start_height); - // Create the protocol_metadata entries here - if (b.major_version >= HF_VERSION_CARROT) { + if (!carrot_yield_payouts.empty()) { for (const auto& yield_entry: carrot_yield_payouts) { cryptonote::protocol_data_entry entry; entry.amount_burnt = yield_entry.second; @@ -2020,6 +2026,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, entry.origin_height = start_height; entry.return_view_tag = yield_entry.first.return_view_tag; entry.return_anchor_enc = yield_entry.first.return_anchor_enc; + entry.is_carrot = true; protocol_entries.push_back(entry); } } else { @@ -2040,6 +2047,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, entry.P_change = yield_entry.first.P_change; entry.return_pubkey = yield_entry.first.return_pubkey; entry.origin_height = start_height; + entry.is_carrot = false; protocol_entries.push_back(entry); } } @@ -4715,7 +4723,7 @@ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vecto if (!yield_entries.size()) { // Report error and abort - LOG_ERROR("calculate_yield_payouts() called, but no yield TXs found at height " << start_height << " - aborting"); + LOG_ERROR("calculate_yield_payouts() called for carrot, but no yield TXs found at height " << start_height << " - aborting"); return false; } @@ -4751,10 +4759,14 @@ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vecto // Iterate over the yield_container, adding each proportion of the yield for (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; - entry.second += yield_128.convert_to(); + uint64_t yield_u64 = boost::numeric_cast(yield_128); + + if (entry.second + yield_u64 < entry.second) { + throw std::overflow_error("uint64_t addition overflow"); + } + entry.second += yield_u64; } } @@ -4813,10 +4825,14 @@ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vecto // Iterate over the yield_container, adding each proportion of the yield for (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; - entry.second += yield_128.convert_to(); + uint64_t yield_u64 = boost::numeric_cast(yield_128); + + if (entry.second + yield_u64 < entry.second) { + throw std::overflow_error("uint64_t addition overflow"); + } + entry.second += yield_u64; } } diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 0925ed42f..60799ccc7 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -347,10 +347,12 @@ namespace cryptonote // Clear the TX contents tx.set_null(); tx.version = 2; + bool carrot_found = false; + bool noncarrot_found = false; tx.type = cryptonote::transaction_type::PROTOCOL; - const bool do_carrot = hard_fork_version >= HF_VERSION_CARROT; - if (do_carrot) + const bool force_carrot = hard_fork_version >= HF_VERSION_ENFORCE_CARROT; + if (force_carrot) { try { @@ -371,7 +373,7 @@ namespace cryptonote memcpy(e.enote_ephemeral_pubkey.data, entry.return_pubkey.data, sizeof(crypto::public_key)); enotes.push_back(e); } - + carrot_found = true; tx = store_carrot_to_coinbase_transaction_v1(enotes, std::string{}, cryptonote::transaction_type::PROTOCOL, height); tx.amount_burnt = 0; tx.invalidate_hashes(); @@ -394,14 +396,31 @@ namespace cryptonote std::vector additional_tx_public_keys; for (auto const& entry: protocol_data) { if (entry.type == cryptonote::transaction_type::STAKE) { + // PAYOUT LOG_PRINT_L2("Yield TX payout submitted " << entry.amount_burnt << entry.source_asset); + + if (entry.is_carrot) { + tx_out out; + out.amount = entry.amount_burnt; + out.target = txout_to_carrot_v1 { + .key = entry.return_address, + .asset_type = entry.destination_asset, + .view_tag = entry.return_view_tag, + .encrypted_janus_anchor = entry.return_anchor_enc, + }; - // Create the TX output for this refund - tx_out out; - cryptonote::set_tx_out(entry.amount_burnt, entry.destination_asset, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, entry.return_address, false, crypto::view_tag{}, out); - additional_tx_public_keys.push_back(entry.return_pubkey); - tx.vout.push_back(out); + additional_tx_public_keys.push_back(entry.return_pubkey); + tx.vout.push_back(out); + carrot_found = true; + } else { + // Create the TX output for this refund + tx_out out; + cryptonote::set_tx_out(entry.amount_burnt, entry.destination_asset, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, entry.return_address, false, crypto::view_tag{}, out); + additional_tx_public_keys.push_back(entry.return_pubkey); + tx.vout.push_back(out); + noncarrot_found = true; + } } else if (entry.type == cryptonote::transaction_type::AUDIT) { // PAYOUT LOG_PRINT_L2("Audit TX payout submitted " << entry.amount_burnt << entry.source_asset); @@ -411,9 +430,19 @@ namespace cryptonote cryptonote::set_tx_out(entry.amount_burnt, entry.destination_asset, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, entry.return_address, false, crypto::view_tag{}, out); additional_tx_public_keys.push_back(entry.return_pubkey); tx.vout.push_back(out); + noncarrot_found = true; } } + if (carrot_found && noncarrot_found) { + LOG_ERROR("Cannot mix Carrot and non-Carrot outputs in the same protocol transaction"); + return false; + } + if (carrot_found) { + // Ensure the TX version is correct + tx.version = TRANSACTION_VERSION_CARROT; + } + // Add in all of the additional TX pubkeys we need to process the payments add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); @@ -565,6 +594,7 @@ namespace cryptonote case HF_VERSION_AUDIT2: case HF_VERSION_AUDIT2_PAUSE: case HF_VERSION_CARROT: + case HF_VERSION_ENFORCE_CARROT: // SRCG: subtract 20% that will be rewarded to staking users CHECK_AND_ASSERT_MES(tx.amount_burnt == 0, false, "while creating outs: amount_burnt is nonzero"); tx.amount_burnt = amount / 5; diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index ec665bd63..7fff2fd38 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -71,6 +71,7 @@ namespace cryptonote carrot::view_tag_t return_view_tag; carrot::encrypted_janus_anchor_t return_anchor_enc; uint64_t origin_height; + bool is_carrot; }; //--------------------------------------------------------------- diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 1abab57e6..03359be0f 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -92,6 +92,9 @@ const hardfork_t testnet_hard_forks[] = { // version 10 Carrot - including treasury mint - starts from block 1100 {10, 1100, 0, 1739780005 }, + + // version 11 Carrot and CryptoNote starts from block 1200 + {11, 1200, 0, 1756811153 }, }; const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); const uint64_t testnet_hard_fork_version_1_till = ((uint64_t)-1);