From 1eb01bb388398d4550eeebac5bfa5c687d625921 Mon Sep 17 00:00:00 2001 From: wowario Date: Thu, 30 Jul 2020 17:50:09 +0300 Subject: [PATCH 1/3] Dynamic Unlock from HF 16 --- src/cryptonote_config.h | 2 +- src/cryptonote_core/blockchain.cpp | 23 ++++++++++++++++++++- src/cryptonote_core/cryptonote_tx_utils.cpp | 10 ++++++++- src/wallet/wallet2.cpp | 6 ++++-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 86383ffa2..b65fdce8d 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -41,7 +41,6 @@ #define CRYPTONOTE_MAX_TX_SIZE 1000000 #define CRYPTONOTE_MAX_TX_PER_BLOCK 0x10000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 -#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 #define CURRENT_TRANSACTION_VERSION 2 #define CURRENT_BLOCK_MAJOR_VERSION 7 #define CURRENT_BLOCK_MINOR_VERSION 7 @@ -178,6 +177,7 @@ #define HF_VERSION_REJECT_SIGS_IN_COINBASE 15 #define HF_VERSION_ENFORCE_MIN_AGE 15 #define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 15 +#define HF_VERSION_DYNAMIC_UNLOCK 16 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 023d4a3fc..3cbaa251d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1384,7 +1384,28 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, return false; } MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx)); - CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + + // Dynamic unlock time from HF 16 + // To calculate unlock window, use first 3 characters of tx public key (convert to decimal), multiply by 2, plus 288 + // Unlock minimum 1 day, maximum ~1 month + // unlock time = unlock_window + height + if (hf_version >= HF_VERSION_DYNAMIC_UNLOCK) + { + crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); + std::string hex_str = epee::string_tools::pod_to_hex(tx_pub_key).substr(0, 3); + uint64_t unlock_window = (std::stol(hex_str,nullptr,16) * 2) + 288; + if (b.miner_tx.unlock_time != height + unlock_window) { + MWARNING("Coinbase transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + unlock_window << + ", unlock window based on first 3 characters of transaction public key, multiply by 2, plus 288."); + return false; + } + LOG_PRINT_L1("+++++ MINER TX UNLOCK TIME INFO\n" << "Height: " << height << ", Unlock window: " << unlock_window << ", Unlock time: " << b.miner_tx.unlock_time << + "\nMiner TX:\t" << get_transaction_hash(b.miner_tx) << "\nTX Pub Key:\t" << tx_pub_key << + "\nHex: " << hex_str << ", Decimal: " << std::stol(hex_str,nullptr,16)); + } else { + CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + 60, false, "coinbase transaction transaction has the wrong unlock time=" + << b.miner_tx.unlock_time << ", expected " << height + 60); + } //check outs overflow //NOTE: not entirely sure this is necessary, given that this function is diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 7b1b74830..b0a5c2968 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -167,7 +167,15 @@ namespace cryptonote tx.version = 1; //lock - tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + if (hard_fork_version >= HF_VERSION_DYNAMIC_UNLOCK) + { + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + std::string hex_str = epee::string_tools::pod_to_hex(tx_pub_key).substr(0, 3); + uint64_t unlock_window = (std::stol(hex_str,nullptr,16) * 2) + 288; + tx.unlock_time = height + unlock_window; + } else { + tx.unlock_time = height + 60; + } tx.vin.push_back(in); tx.invalidate_hashes(); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e97e37837..6c8e01f1d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8097,7 +8097,8 @@ void wallet2::get_outs(std::vector> const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + // unlock window max is (4095*2)+288 = 8478 (~29 days) + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 8478 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); bool use_histogram = amount != 0 || !has_rct_distribution; @@ -8413,7 +8414,8 @@ void wallet2::get_outs(std::vector> for(size_t idx: selected_transfers) { const transfer_details &td = m_transfers[idx]; - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + // unlock window max is (4095*2)+288 = 8478 (~29 days) + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 8478 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); outs.push_back(std::vector()); outs.back().reserve(fake_outputs_count + 1); const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); From 5b46a0df6dc95aeec800a7a9aa462bd7615987e2 Mon Sep 17 00:00:00 2001 From: wowario Date: Sat, 1 Aug 2020 09:40:24 +0300 Subject: [PATCH 2/3] add generated coins and height to unlock calculation --- src/cryptonote_core/blockchain.cpp | 15 +++++++++------ src/cryptonote_core/cryptonote_tx_utils.cpp | 5 ++++- src/wallet/wallet2.cpp | 8 ++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 3cbaa251d..bfb80c850 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1386,22 +1386,25 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx)); // Dynamic unlock time from HF 16 - // To calculate unlock window, use first 3 characters of tx public key (convert to decimal), multiply by 2, plus 288 - // Unlock minimum 1 day, maximum ~1 month + // To calculate unlock window, use first 3 characters of tx public key (convert hex to decimal), + // multiply by 2, plus last 3 numbers of already_generated_coins, plus last numbers of block height, plus 288 + // Unlock minimum 1 day (288 blocks), maximum is ~33 days ((4095*2)+999+99+288 = 9576 blocks) // unlock time = unlock_window + height if (hf_version >= HF_VERSION_DYNAMIC_UNLOCK) { crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); std::string hex_str = epee::string_tools::pod_to_hex(tx_pub_key).substr(0, 3); - uint64_t unlock_window = (std::stol(hex_str,nullptr,16) * 2) + 288; + uint64_t pub_key_num = std::stol(hex_str,nullptr,16) * 2; + uint64_t coin_gen_num = m_db->get_block_already_generated_coins(height - 1) % 1000; + uint64_t h_num = height % 100; + uint64_t unlock_window = pub_key_num + coin_gen_num + h_num + 288; if (b.miner_tx.unlock_time != height + unlock_window) { - MWARNING("Coinbase transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + unlock_window << - ", unlock window based on first 3 characters of transaction public key, multiply by 2, plus 288."); + MWARNING("Coinbase transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + unlock_window); return false; } LOG_PRINT_L1("+++++ MINER TX UNLOCK TIME INFO\n" << "Height: " << height << ", Unlock window: " << unlock_window << ", Unlock time: " << b.miner_tx.unlock_time << "\nMiner TX:\t" << get_transaction_hash(b.miner_tx) << "\nTX Pub Key:\t" << tx_pub_key << - "\nHex: " << hex_str << ", Decimal: " << std::stol(hex_str,nullptr,16)); + "\nPub Key Num: " << pub_key_num << ", Coin Gen Num: " << coin_gen_num << ", Height Num: " << h_num); } else { CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + 60, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + 60); diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index b0a5c2968..0020df89d 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -171,7 +171,10 @@ namespace cryptonote { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); std::string hex_str = epee::string_tools::pod_to_hex(tx_pub_key).substr(0, 3); - uint64_t unlock_window = (std::stol(hex_str,nullptr,16) * 2) + 288; + uint64_t pub_key_num = std::stol(hex_str,nullptr,16) * 2; + uint64_t coin_gen_num = already_generated_coins % 1000; + uint64_t h_num = height % 100; + uint64_t unlock_window = pub_key_num + coin_gen_num + h_num + 288; tx.unlock_time = height + unlock_window; } else { tx.unlock_time = height + 60; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6c8e01f1d..c084ea123 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8097,8 +8097,8 @@ void wallet2::get_outs(std::vector> const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - // unlock window max is (4095*2)+288 = 8478 (~29 days) - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 8478 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + // maximum unlock is ~33 days ((4095*2)+999+99+288 = 9576 blocks + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 9576 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); bool use_histogram = amount != 0 || !has_rct_distribution; @@ -8414,8 +8414,8 @@ void wallet2::get_outs(std::vector> for(size_t idx: selected_transfers) { const transfer_details &td = m_transfers[idx]; - // unlock window max is (4095*2)+288 = 8478 (~29 days) - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 8478 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + // maximum unlock is ~33 days ((4095*2)+999+99+288 = 9576 blocks + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 9576 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); outs.push_back(std::vector()); outs.back().reserve(fake_outputs_count + 1); const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); From 8757282908fa5bf49666f53bceb2c369db414e5f Mon Sep 17 00:00:00 2001 From: wowario Date: Fri, 7 Aug 2020 21:34:19 +0300 Subject: [PATCH 3/3] use block id --- src/cryptonote_core/blockchain.cpp | 28 ++++++++++----------- src/cryptonote_core/cryptonote_tx_utils.cpp | 12 ++++----- src/cryptonote_core/cryptonote_tx_utils.h | 3 ++- src/wallet/wallet2.cpp | 8 +++--- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index bfb80c850..b3cdcf005 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1386,25 +1386,25 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx)); // Dynamic unlock time from HF 16 - // To calculate unlock window, use first 3 characters of tx public key (convert hex to decimal), - // multiply by 2, plus last 3 numbers of already_generated_coins, plus last numbers of block height, plus 288 - // Unlock minimum 1 day (288 blocks), maximum is ~33 days ((4095*2)+999+99+288 = 9576 blocks) + // To calculate unlock window, get the block hash at height-1337, convert the + // first 3 characters from hexadecimal to decimal, multiply by 2, and then add 288. + // Unlock minimum 1 day (288 blocks), maximum is ~29 days ((4095*2)+288 = 8478 blocks) // unlock time = unlock_window + height if (hf_version >= HF_VERSION_DYNAMIC_UNLOCK) { - crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); - std::string hex_str = epee::string_tools::pod_to_hex(tx_pub_key).substr(0, 3); - uint64_t pub_key_num = std::stol(hex_str,nullptr,16) * 2; - uint64_t coin_gen_num = m_db->get_block_already_generated_coins(height - 1) % 1000; - uint64_t h_num = height % 100; - uint64_t unlock_window = pub_key_num + coin_gen_num + h_num + 288; + crypto::hash blk_id = get_block_id_by_height(height-1337); + std::string hex_str = epee::string_tools::pod_to_hex(blk_id).substr(0, 3); + uint64_t blk_num = std::stol(hex_str,nullptr,16)*2; + uint64_t unlock_window = blk_num + 288; + if (b.miner_tx.unlock_time != height + unlock_window) { MWARNING("Coinbase transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + unlock_window); return false; } - LOG_PRINT_L1("+++++ MINER TX UNLOCK TIME INFO\n" << "Height: " << height << ", Unlock window: " << unlock_window << ", Unlock time: " << b.miner_tx.unlock_time << - "\nMiner TX:\t" << get_transaction_hash(b.miner_tx) << "\nTX Pub Key:\t" << tx_pub_key << - "\nPub Key Num: " << pub_key_num << ", Coin Gen Num: " << coin_gen_num << ", Height Num: " << h_num); + LOG_PRINT_L1("+++++ MINER TX UNLOCK TIME INFO" << + "\nHeight: " << height << ", Unlock window: " << unlock_window << ", Unlock time: " << b.miner_tx.unlock_time << + "\nblk_height: " << height-1337 << ", blk_id: " << blk_id << + "\nhex_str: " << hex_str << ", blk_num: " << blk_num); } else { CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + 60, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + 60); @@ -1746,7 +1746,7 @@ 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; - bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); + bool r = construct_miner_tx(this, height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, 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); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -1755,7 +1755,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, #endif for (size_t try_count = 0; try_count != 10; ++try_count) { - r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); + r = construct_miner_tx(this, height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance"); size_t coinbase_weight = get_transaction_weight(b.miner_tx); diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 0020df89d..5c539f4d1 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -76,7 +76,7 @@ namespace cryptonote LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << " subaddresses"); } //--------------------------------------------------------------- - 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, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { + bool construct_miner_tx(const Blockchain *pb, 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, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { tx.vin.clear(); tx.vout.clear(); tx.extra.clear(); @@ -169,12 +169,10 @@ namespace cryptonote //lock if (hard_fork_version >= HF_VERSION_DYNAMIC_UNLOCK) { - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - std::string hex_str = epee::string_tools::pod_to_hex(tx_pub_key).substr(0, 3); - uint64_t pub_key_num = std::stol(hex_str,nullptr,16) * 2; - uint64_t coin_gen_num = already_generated_coins % 1000; - uint64_t h_num = height % 100; - uint64_t unlock_window = pub_key_num + coin_gen_num + h_num + 288; + crypto::hash blk_id = pb->get_block_id_by_height(height-1337); + std::string hex_str = epee::string_tools::pod_to_hex(blk_id).substr(0, 3); + uint64_t blk_num = std::stol(hex_str,nullptr,16)*2; + uint64_t unlock_window = blk_num + 288; tx.unlock_time = height + unlock_window; } else { tx.unlock_time = height + 60; diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 3622029e0..68c1e8aa4 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -37,7 +37,8 @@ namespace cryptonote { //--------------------------------------------------------------- - 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, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); + class Blockchain; + bool construct_miner_tx(const Blockchain *pb, 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, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); struct tx_source_entry { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c084ea123..fee165981 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8097,8 +8097,8 @@ void wallet2::get_outs(std::vector> const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - // maximum unlock is ~33 days ((4095*2)+999+99+288 = 9576 blocks - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 9576 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + // Unlock minimum 1 day (288 blocks), maximum is ~29 days ((4095*2)+288 = 8478 blocks) + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 8478 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); bool use_histogram = amount != 0 || !has_rct_distribution; @@ -8414,8 +8414,8 @@ void wallet2::get_outs(std::vector> for(size_t idx: selected_transfers) { const transfer_details &td = m_transfers[idx]; - // maximum unlock is ~33 days ((4095*2)+999+99+288 = 9576 blocks - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 9576 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + // Unlock minimum 1 day (288 blocks), maximum is ~29 days ((4095*2)+288 = 8478 blocks) + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? 8478 - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); outs.push_back(std::vector()); outs.back().reserve(fake_outputs_count + 1); const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());