treasury reward payout added for HF11

This commit is contained in:
Some Random Crypto Guy
2026-03-25 19:55:35 +00:00
parent 6fb167308c
commit 83934efaad
4 changed files with 196 additions and 30 deletions
+13 -1
View File
@@ -94,6 +94,10 @@
#define CREATE_TOKEN_LOCK_PERIOD 10
// HF11 block reward split
#define BLOCK_REWARD_TREASURY_PCT 25 // to treasury, 21600-block unlock
#define BLOCK_REWARD_STAKER_PCT 20 // to stakers via yield
#define DIFFICULTY_TARGET_V2 120 // seconds
#define DIFFICULTY_TARGET_V1 60 // seconds - before first fork
#define DIFFICULTY_WINDOW_V2 70 // blocks
@@ -319,6 +323,7 @@ namespace config
const uint64_t STAKE_LOCK_PERIOD = 30*24*30;
const uint64_t TREASURY_SAL1_MINT_PERIOD = 30*24*30; // 1 month of blocks
std::string const TREASURY_ADDRESS_CARROT = "SC11sFBPrGmNuT8AiTPUW479BwkdPJwBxdjKEhZ96yDfFg3B4mawgcpE1YfCAa1zwzUiRTMP9eqB54av48ALhzUu1Q5QoPGUfh";
std::string const TREASURY_ADDRESS = "SaLvdZR6w1A21sf2Wh6jYEh1wzY4GSbT7RX6FjyPsnLsffWLrzFQeXUXJcmBLRWDzZC2YXeYe5t7qKsnrg9FpmxmEcxPHsEYfqA";
// treasury payout {tx-key, output-key, anchor_enc, vie_tag} tuples
@@ -423,9 +428,11 @@ namespace config
"KWv3Vo1/Gny+1vfaxsXhBQiG1KlHkafNGarzoL0WHW4ocqaaqF5iv8i35A==\n"
"-----END PUBLIC KEY-----\n";
std::string const TREASURY_ADDRESS = "SaLvTyLFta9BiAXeUfFkKvViBkFt4ay5nEUBpWyDKewYggtsoxBbtCUVqaBjtcCDyY1euun8Giv7LLEgvztuurLo5a6Km1zskZn36";
std::string const TREASURY_ADDRESS_CARROT = "SC1TouvX6e4HmkAqsU6AAXLRjgeZnnKHjSpVfMHepTXramNMT2P47AsDmteLH81wdPR2DwMg3cxKvgrhUBeDSUW6MhM3sQb92we";
std::string const TREASURY_ADDRESS = "SaLvTyL2pN2SCAPRxwDQ7qjhdg46VbhZrGZTp2wHKJ5sK434a8ivEH35eWp2FTcmyW6LY6ExfBb9chmQ9xAL1eJyZ5FQjtQGTis3v";
// treasury payout {tx-key, output-key, anchor_enc, vie_tag} tuples
// check address type before future development
const std::map<uint64_t, std::tuple<std::string, std::string, std::string, std::string>> TREASURY_SAL1_MINT_OUTPUT_DATA = {
{1100, {"71336a480440ed24a53b2cfd5a5292d1e618c3e843637227883ea2cc42fb346f","a135f59a05ea9e33539e4502b187b4789cc7fba79616c6902a902cc6601f0359","7f1c6970232254a9b13f7f063df2a853","62073c"}},
{1120, {"2cc49b182addc0106b601c9876c01a4b06532c05f9dd9179b2c4f47e5c7c0d74","fd62e59324389f37d0bb628a39f413c11be34d572ec3a40f465e008b9dfd5e0c","fbf8584222e299bb748009eaf2177123","b76a8d"}},
@@ -467,6 +474,7 @@ namespace config
"KWv3Vo1/Gny+1vfaxsXhBQiG1KlHkafNGarzoL0WHW4ocqaaqF5iv8i35A==\n"
"-----END PUBLIC KEY-----\n";
std::string const TREASURY_ADDRESS_CARROT = ""; // TODO: generate stagenet Carrot treasury address ?
std::string const TREASURY_ADDRESS = "fuLMowH85abK8nz9BBMEem7MAfUbQu4aSHHUV9j5Z86o6Go9Lv2U5ZQiJCWPY9R9HA8p5idburazjAhCqDngLo7fYPCD9ciM9ee1A";
// treasury payout {tx-key, output-key, anchor_enc, view_tag} tuples
@@ -521,6 +529,7 @@ namespace cryptonote
uint64_t TREASURY_SAL1_MINT_PERIOD;
std::map<uint8_t, std::pair<uint64_t, std::pair<std::string, std::string>>> const AUDIT_HARD_FORKS;
std::string TREASURY_ADDRESS;
std::string TREASURY_ADDRESS_CARROT;
std::map<uint64_t, std::tuple<std::string, std::string, std::string, std::string>> TREASURY_SAL1_MINT_OUTPUT_DATA;
};
inline const config_t& get_config(network_type nettype)
@@ -544,6 +553,7 @@ namespace cryptonote
::config::TREASURY_SAL1_MINT_PERIOD,
::config::AUDIT_HARD_FORKS,
::config::TREASURY_ADDRESS,
::config::TREASURY_ADDRESS_CARROT,
::config::TREASURY_SAL1_MINT_OUTPUT_DATA
};
static const config_t testnet = {
@@ -565,6 +575,7 @@ namespace cryptonote
::config::testnet::TREASURY_SAL1_MINT_PERIOD,
::config::testnet::AUDIT_HARD_FORKS,
::config::testnet::TREASURY_ADDRESS,
::config::testnet::TREASURY_ADDRESS_CARROT,
::config::testnet::TREASURY_SAL1_MINT_OUTPUT_DATA
};
static const config_t stagenet = {
@@ -586,6 +597,7 @@ namespace cryptonote
::config::stagenet::TREASURY_SAL1_MINT_PERIOD,
::config::stagenet::AUDIT_HARD_FORKS,
::config::stagenet::TREASURY_ADDRESS,
::config::stagenet::TREASURY_ADDRESS_CARROT,
::config::stagenet::TREASURY_SAL1_MINT_OUTPUT_DATA
};
switch (nettype)
+94 -5
View File
@@ -58,6 +58,8 @@
#include "crypto/hash.h"
#include "cryptonote_core.h"
#include "ringct/rctSigs.h"
#include "carrot_core/payment_proposal.h"
#include "carrot_impl/format_utils.h"
#include "common/perf_timer.h"
#include "common/notify.h"
#include "common/varint.h"
@@ -1517,6 +1519,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
for(size_t i = 0; i < b.miner_tx.vout.size(); i++)
{
// skip the treasury output
// check
if (treasury_payout_exists && (i == treasury_index_in_tx_outputs)) {
continue;
}
@@ -1535,7 +1538,6 @@ 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_ENABLE_TOKENS:
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;
@@ -1543,6 +1545,95 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
if (already_generated_coins != 0)
CHECK_AND_ASSERT_MES(money_in_use / 5 == b.miner_tx.amount_burnt, false, "miner_transaction has incorrect amount_burnt amount");
break;
case HF_VERSION_ENABLE_TOKENS:
// HF11: block reward split is 60% miner + 25% treasury + 215% staker (amount_burnt)
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;
}
if (already_generated_coins != 0) {
// Validate treasury share: one output must equal block_reward * 25 / 100
uint64_t expected_treasury = money_in_use * BLOCK_REWARD_TREASURY_PCT / 100;
// Validate staker share: amount_burnt == block_reward * 20 / 100
uint64_t expected_staker = (money_in_use - expected_treasury) * BLOCK_REWARD_STAKER_PCT / 100;
CHECK_AND_ASSERT_MES(expected_staker == b.miner_tx.amount_burnt, false,
"miner_transaction has incorrect amount_burnt for HF11 (expected " << expected_staker << ", got " << b.miner_tx.amount_burnt << ")");
uint64_t expected_miner = money_in_use - b.miner_tx.amount_burnt - expected_treasury;
bool found_treasury = false;
bool found_miner = false;
for (size_t i = 0; i < b.miner_tx.vout.size(); i++) {
if (treasury_payout_exists && (i == treasury_index_in_tx_outputs)) continue;
if (b.miner_tx.vout[i].amount == expected_treasury) found_treasury = true; //check here
if (b.miner_tx.vout[i].amount == expected_miner) found_miner = true;
}
CHECK_AND_ASSERT_MES(found_treasury, false, "miner_transaction missing treasury output with expected amount " << expected_treasury);
CHECK_AND_ASSERT_MES(found_miner, false, "miner_transaction missing miner output with expected amount " << expected_miner);
// Verify treasury output full? using deterministic anchor
{
// treasury_destination
address_parse_info treasury_addr_info;
bool addr_ok = cryptonote::get_account_address_from_str(treasury_addr_info, m_nettype, get_config(m_nettype).TREASURY_ADDRESS_CARROT);
CHECK_AND_ASSERT_MES(addr_ok, false, "Failed to parse treasury address for validation");
carrot::CarrotDestinationV1 treasury_destination;
carrot::make_carrot_main_address_v1(treasury_addr_info.address.m_spend_public_key,
treasury_addr_info.address.m_view_public_key,
treasury_destination);
// deterministic janus anchor
const keypair det_keys = get_deterministic_keypair_from_height(height);
carrot::janus_anchor_t treasury_anchor{};
memcpy(treasury_anchor.bytes, det_keys.sec.data, sizeof(treasury_anchor.bytes));
const carrot::CarrotPaymentProposalV1 treasury_proposal{
.destination = treasury_destination,
.amount = expected_treasury,
.asset_type = "SAL1",
.randomness = treasury_anchor
};
carrot::CarrotCoinbaseEnoteV1 expected_enote;
carrot::get_coinbase_output_proposal_v1(treasury_proposal, height, expected_enote);
// Validate treasury output: K_o, view_tag, anchor_enc, and D_e
bool found_valid_treasury = false;
// ephemeral pubkeys
crypto::public_key single_tx_pubkey = cryptonote::get_tx_pub_key_from_extra(b.miner_tx.extra);
bool has_single = (single_tx_pubkey != crypto::null_pkey);
std::vector<crypto::public_key> additional_tx_pubkeys = cryptonote::get_additional_tx_pub_keys_from_extra(b.miner_tx.extra);
for (size_t i = 0; i < b.miner_tx.vout.size(); i++) {
if (treasury_payout_exists && (i == treasury_index_in_tx_outputs)) continue;
if (b.miner_tx.vout[i].target.type() != typeid(txout_to_carrot_v1)) continue;
const auto &carrot_out = boost::get<txout_to_carrot_v1>(b.miner_tx.vout[i].target);
// Check K_o
if (carrot_out.key != expected_enote.onetime_address) continue;
// Check view_tag
CHECK_AND_ASSERT_MES(carrot_out.view_tag == expected_enote.view_tag, false,
"treasury output view_tag mismatch (burning bug: K_o correct but view_tag tampered)"); CHECK_AND_ASSERT_MES(0 == memcmp(&carrot_out.encrypted_janus_anchor, &expected_enote.anchor_enc, sizeof(expected_enote.anchor_enc)), false,
"treasury output anchor_enc mismatch (burning bug: K_o correct but anchor tampered)");
// Check D_e
const crypto::public_key expected_De = carrot::raw_byte_convert<crypto::public_key>(expected_enote.enote_ephemeral_pubkey);
crypto::public_key actual_De{};
if (!additional_tx_pubkeys.empty() && i < additional_tx_pubkeys.size()) {
actual_De = additional_tx_pubkeys[i];
} else if (has_single) {
actual_De = single_tx_pubkey;
}
CHECK_AND_ASSERT_MES(actual_De == expected_De, false,
"treasury output D_e mismatch"); //important
found_valid_treasury = true;
break;
}
CHECK_AND_ASSERT_MES(found_valid_treasury, false,
"miner_transaction treasury output has wrong onetime address (K_o mismatch)");
}
}
break;
default:
assert(false);
break;
@@ -2237,9 +2328,6 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block,
*/
// Time to construct the protocol_tx
address_parse_info treasury_address_info;
ok = cryptonote::get_account_address_from_str(treasury_address_info, m_nettype, get_config(m_nettype).TREASURY_ADDRESS);
CHECK_AND_ASSERT_MES(ok, false, "Failed to obtain treasury address info");
ok = construct_protocol_tx(height, b.protocol_tx, protocol_entries, b.major_version);
CHECK_AND_ASSERT_MES(ok, false, "Failed to construct protocol tx");
@@ -2303,7 +2391,8 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block,
*/
//make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight
uint8_t hf_version = b.major_version;
size_t max_outs = hf_version >= 4 ? 1 : 11;
size_t max_outs = hf_version >= HF_VERSION_ENABLE_TOKENS ? 3 : (hf_version >= 4 ? 1 : 11);
bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, m_nettype, m_hardfork->get_hardforks(), ex_nonce, max_outs, hf_version);
CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance");
size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx);
+86 -23
View File
@@ -459,6 +459,30 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
keypair get_deterministic_keypair_from_height(uint64_t height)
{
keypair k;
ec_scalar& sec = k.sec;
// Encode height as little-endian into the first 8 bytes of the secret key
for (int i = 0; i < 8; i++)
{
uint64_t height_byte = height & ((uint64_t)0xFF << (i * 8));
uint8_t byte = height_byte >> (i * 8);
sec.data[i] = byte;
}
// Zero-pad the remaining 24 bytes
for (int i = 8; i < 32; i++)
{
sec.data[i] = 0x00;
}
// Derive a valid Ed25519 keypair: hash/clamp the seed, compute public key
generate_keys(k.pub, k.sec, k.sec, true);
return k;
}
//---------------------------------------------------------------
bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, network_type nettype, const std::vector<hardfork_t>& hardforks, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) {
// Clear the TX contents
@@ -487,33 +511,72 @@ namespace cryptonote
{
try
{
// Build the miner payout
carrot::CarrotDestinationV1 destination;
// miner destination
carrot::CarrotDestinationV1 miner_destination;
carrot::make_carrot_main_address_v1(miner_address.m_spend_public_key,
miner_address.m_view_public_key,
destination);
miner_destination);
CHECK_AND_ASSERT_THROW_MES(!destination.is_subaddress,
"make_single_enote_carrot_coinbase_transaction_v1: subaddress are not allowed in miner transactions");
CHECK_AND_ASSERT_THROW_MES(destination.payment_id == carrot::null_payment_id,
"make_single_enote_carrot_coinbase_transaction_v1: integrated addresses are not allowed in miner transactions");
CHECK_AND_ASSERT_THROW_MES(!miner_destination.is_subaddress,
"construct_miner_tx: subaddresses are not allowed in miner transactions");
CHECK_AND_ASSERT_THROW_MES(miner_destination.payment_id == carrot::null_payment_id,
"construct_miner_tx: integrated addresses are not allowed in miner transactions");
uint64_t stake_reward = block_reward / 5;
const bool do_new_split = (hard_fork_version >= HF_VERSION_ENABLE_TOKENS);
uint64_t treasury_reward = do_new_split ? (block_reward * BLOCK_REWARD_TREASURY_PCT / 100) : 0;
uint64_t stake_reward = (block_reward - treasury_reward) * BLOCK_REWARD_STAKER_PCT / 100;
uint64_t miner_reward = (block_reward - treasury_reward - stake_reward);
const carrot::CarrotPaymentProposalV1 payment_proposal{
.destination = destination,
.amount = block_reward - stake_reward,
//miner enote
const carrot::CarrotPaymentProposalV1 miner_proposal{
.destination = miner_destination,
.amount = miner_reward,
.asset_type = "SAL1",
.randomness = carrot::gen_janus_anchor()
};
std::vector<carrot::CarrotCoinbaseEnoteV1> enotes(treasury_payout_exists ? 2 : 1);
carrot::get_coinbase_output_proposal_v1(payment_proposal, height, enotes.front());
// Determine number of enotes: miner + optional treasury_reward + optional treasury_mint
size_t num_enotes = 1;
if (do_new_split) num_enotes++; // treasury reward output
if (treasury_payout_exists) num_enotes++;
std::vector<carrot::CarrotCoinbaseEnoteV1> enotes(num_enotes);
carrot::get_coinbase_output_proposal_v1(miner_proposal, height, enotes[0]);
// Check to see if there needs to be a treasury payout
size_t enote_idx = 1;
// Add the treasury reward enote (25% of block reward) for HF11+
if (do_new_split) {
//treasury address for HF11+ block reward split
address_parse_info treasury_addr_info;
bool treasury_ok = cryptonote::get_account_address_from_str(treasury_addr_info, nettype, get_config(nettype).TREASURY_ADDRESS_CARROT);
CHECK_AND_ASSERT_MES(treasury_ok, false, "Failed to parse treasury address for block reward split"); // maybe more check can be added here, but it's enough for now (bcs validation)
carrot::CarrotDestinationV1 treasury_destination;
carrot::make_carrot_main_address_v1(treasury_addr_info.address.m_spend_public_key,
treasury_addr_info.address.m_view_public_key,
treasury_destination);
// Derive a deterministic janus anchor from height so validators can independently recompute the expected treasury K_o
const keypair det_keys = get_deterministic_keypair_from_height(height);
carrot::janus_anchor_t treasury_anchor{};
static_assert(sizeof(treasury_anchor.bytes) <= sizeof(det_keys.sec.data),
"janus anchor must fit in secret key");
memcpy(treasury_anchor.bytes, det_keys.sec.data, sizeof(treasury_anchor.bytes));
const carrot::CarrotPaymentProposalV1 treasury_proposal{
.destination = treasury_destination,
.amount = treasury_reward,
.asset_type = "SAL1",
.randomness = treasury_anchor
};
carrot::get_coinbase_output_proposal_v1(treasury_proposal, height, enotes[enote_idx]);
enote_idx++;
}
//
if (treasury_payout_exists) {
// Convert the strings into meaningful data
const auto [tx_public_key_str, onetime_address_str, anchor_enc_str, view_tag_str] = treasury_payout_data.at(height);
mx25519_pubkey tx_public_key;
CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(tx_public_key_str, tx_public_key), "fail to deserialize treasury tx public key");
@@ -524,8 +587,8 @@ namespace cryptonote
carrot::view_tag_t view_tag;
CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(view_tag_str, view_tag), "fail to deserialize treasury tx view_tag");
// Manually produce an enote for the treasury payout using the hardcoded keys
carrot::CarrotCoinbaseEnoteV1 &treasury_enote = enotes.back();
//
carrot::CarrotCoinbaseEnoteV1 &treasury_enote = enotes[enote_idx];
treasury_enote.onetime_address = onetime_address;
treasury_enote.amount = TREASURY_SAL1_MINT_AMOUNT;
treasury_enote.asset_type = "SAL1";
@@ -533,12 +596,12 @@ namespace cryptonote
treasury_enote.view_tag = view_tag;
treasury_enote.enote_ephemeral_pubkey = tx_public_key;
treasury_enote.block_index = height;
}
// sort enotes by K_o
if (enotes[0].onetime_address > enotes[1].onetime_address) {
std::swap(enotes[0], enotes[1]);
}
}
// Sort enotes by K_o
std::sort(enotes.begin(), enotes.end(), [](const carrot::CarrotCoinbaseEnoteV1 &a, const carrot::CarrotCoinbaseEnoteV1 &b) {
return a.onetime_address < b.onetime_address;
});
tx = carrot::store_carrot_to_coinbase_transaction_v1(enotes, extra_nonce, cryptonote::transaction_type::MINER, height);
tx.version = (hard_fork_version >= HF_VERSION_ENABLE_TOKENS) ? TRANSACTION_VERSION_ENABLE_TOKENS : TRANSACTION_VERSION_CARROT;
@@ -77,6 +77,8 @@ namespace cryptonote
//---------------------------------------------------------------
bool construct_protocol_tx(const size_t height, transaction& tx, std::vector<protocol_data_entry>& protocol_data, const uint8_t hf_version);
//---------------------------------------------------------------
keypair get_deterministic_keypair_from_height(uint64_t height);
//---------------------------------------------------------------
bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, network_type nettype = network_type::FAKECHAIN, const std::vector<hardfork_t>& hardforks = {}, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1);
struct tx_source_entry