blockchain: deterministic UNIX time unlock checks
Based on a patch by TheCharlatan <seb.kung@gmail.com>
This commit is contained in:
parent
9bba1a24ea
commit
4971219c2c
|
@ -180,6 +180,7 @@
|
||||||
#define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12
|
#define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12
|
||||||
#define HF_VERSION_EXACT_COINBASE 13
|
#define HF_VERSION_EXACT_COINBASE 13
|
||||||
#define HF_VERSION_CLSAG 13
|
#define HF_VERSION_CLSAG 13
|
||||||
|
#define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13
|
||||||
|
|
||||||
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
|
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
|
||||||
|
|
||||||
|
|
|
@ -2267,8 +2267,9 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA
|
||||||
MERROR("Unexpected output data size: expected " << req.outputs.size() << ", got " << data.size());
|
MERROR("Unexpected output data size: expected " << req.outputs.size() << ", got " << data.size());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const uint8_t hf_version = m_hardfork->get_current_version();
|
||||||
for (const auto &t: data)
|
for (const auto &t: data)
|
||||||
res.outs.push_back({t.pubkey, t.commitment, is_tx_spendtime_unlocked(t.unlock_time), t.height, crypto::null_hash});
|
res.outs.push_back({t.pubkey, t.commitment, is_tx_spendtime_unlocked(t.unlock_time, hf_version), t.height, crypto::null_hash});
|
||||||
|
|
||||||
if (req.get_txid)
|
if (req.get_txid)
|
||||||
{
|
{
|
||||||
|
@ -2292,7 +2293,8 @@ void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint
|
||||||
key = o_data.pubkey;
|
key = o_data.pubkey;
|
||||||
mask = o_data.commitment;
|
mask = o_data.commitment;
|
||||||
tx_out_index toi = m_db->get_output_tx_and_index(amount, index);
|
tx_out_index toi = m_db->get_output_tx_and_index(amount, index);
|
||||||
unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
|
const uint8_t hf_version = m_hardfork->get_current_version();
|
||||||
|
unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first), hf_version);
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const
|
bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const
|
||||||
|
@ -3363,7 +3365,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
||||||
|
|
||||||
// make sure that output being spent matches up correctly with the
|
// make sure that output being spent matches up correctly with the
|
||||||
// signature spending it.
|
// signature spending it.
|
||||||
if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height))
|
if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height, hf_version))
|
||||||
{
|
{
|
||||||
MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
|
MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
|
||||||
if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain()
|
if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain()
|
||||||
|
@ -3756,7 +3758,7 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
// This function checks to see if a tx is unlocked. unlock_time is either
|
// This function checks to see if a tx is unlocked. unlock_time is either
|
||||||
// a block index or a unix time.
|
// a block index or a unix time.
|
||||||
bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const
|
bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time, uint8_t hf_version) const
|
||||||
{
|
{
|
||||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||||
if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER)
|
if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER)
|
||||||
|
@ -3771,7 +3773,7 @@ bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//interpret as time
|
//interpret as time
|
||||||
uint64_t current_time = static_cast<uint64_t>(time(NULL));
|
const uint64_t current_time = hf_version >= HF_VERSION_DETERMINISTIC_UNLOCK_TIME ? get_adjusted_time(m_db->height()) : static_cast<uint64_t>(time(NULL));
|
||||||
if(current_time + (get_current_hard_fork_version() < 2 ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2) >= unlock_time)
|
if(current_time + (get_current_hard_fork_version() < 2 ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2) >= unlock_time)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
|
@ -3783,7 +3785,7 @@ bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const
|
||||||
// This function locates all outputs associated with a given input (mixins)
|
// This function locates all outputs associated with a given input (mixins)
|
||||||
// and validates that they exist and are usable. It also checks the ring
|
// and validates that they exist and are usable. It also checks the ring
|
||||||
// signature for each input.
|
// signature for each input.
|
||||||
bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height) const
|
bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height, uint8_t hf_version) const
|
||||||
{
|
{
|
||||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||||
|
|
||||||
|
@ -3795,14 +3797,15 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons
|
||||||
{
|
{
|
||||||
std::vector<rct::ctkey >& m_output_keys;
|
std::vector<rct::ctkey >& m_output_keys;
|
||||||
const Blockchain& m_bch;
|
const Blockchain& m_bch;
|
||||||
outputs_visitor(std::vector<rct::ctkey>& output_keys, const Blockchain& bch) :
|
const uint8_t hf_version;
|
||||||
m_output_keys(output_keys), m_bch(bch)
|
outputs_visitor(std::vector<rct::ctkey>& output_keys, const Blockchain& bch, uint8_t hf_version) :
|
||||||
|
m_output_keys(output_keys), m_bch(bch), hf_version(hf_version)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey, const rct::key &commitment)
|
bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey, const rct::key &commitment)
|
||||||
{
|
{
|
||||||
//check tx unlock time
|
//check tx unlock time
|
||||||
if (!m_bch.is_tx_spendtime_unlocked(unlock_time))
|
if (!m_bch.is_tx_spendtime_unlocked(unlock_time, hf_version))
|
||||||
{
|
{
|
||||||
MERROR_VER("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time);
|
MERROR_VER("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time);
|
||||||
return false;
|
return false;
|
||||||
|
@ -3821,7 +3824,7 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons
|
||||||
output_keys.clear();
|
output_keys.clear();
|
||||||
|
|
||||||
// collect output keys
|
// collect output keys
|
||||||
outputs_visitor vi(output_keys, *this);
|
outputs_visitor vi(output_keys, *this, hf_version);
|
||||||
if (!scan_outputkeys_for_indexes(tx_version, txin, vi, tx_prefix_hash, pmax_related_block_height))
|
if (!scan_outputkeys_for_indexes(tx_version, txin, vi, tx_prefix_hash, pmax_related_block_height))
|
||||||
{
|
{
|
||||||
MERROR_VER("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size());
|
MERROR_VER("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size());
|
||||||
|
@ -3840,12 +3843,38 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
//TODO: Is this intended to do something else? Need to look into the todo there.
|
// only works on the main chain
|
||||||
uint64_t Blockchain::get_adjusted_time() const
|
uint64_t Blockchain::get_adjusted_time(uint64_t height) const
|
||||||
{
|
{
|
||||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||||
//TODO: add collecting median time
|
|
||||||
return time(NULL);
|
// if not enough blocks, no proper median yet, return current time
|
||||||
|
if(height < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)
|
||||||
|
{
|
||||||
|
return static_cast<uint64_t>(time(NULL));
|
||||||
|
}
|
||||||
|
std::vector<uint64_t> timestamps;
|
||||||
|
|
||||||
|
// need most recent 60 blocks, get index of first of those
|
||||||
|
size_t offset = height - BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW;
|
||||||
|
timestamps.reserve(height - offset);
|
||||||
|
for(;offset < height; ++offset)
|
||||||
|
{
|
||||||
|
timestamps.push_back(m_db->get_block_timestamp(offset));
|
||||||
|
}
|
||||||
|
uint64_t median_ts = epee::misc_utils::median(timestamps);
|
||||||
|
|
||||||
|
// project the median to match approximately when the block being validated will appear
|
||||||
|
// the median is calculated from a chunk of past blocks, so we use +1 to offset onto the current block
|
||||||
|
median_ts += (BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW + 1) * DIFFICULTY_TARGET_V2 / 2;
|
||||||
|
|
||||||
|
// project the current block's time based on the previous block's time
|
||||||
|
// we don't use the current block's time directly to mitigate timestamp manipulation
|
||||||
|
uint64_t adjusted_current_block_ts = timestamps.back() + DIFFICULTY_TARGET_V2;
|
||||||
|
|
||||||
|
// return minimum of ~current block time and adjusted median time
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
//TODO: revisit, has changed a bit on upstream
|
//TODO: revisit, has changed a bit on upstream
|
||||||
|
@ -3873,9 +3902,9 @@ bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const
|
||||||
bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const
|
bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const
|
||||||
{
|
{
|
||||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||||
if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT)
|
if(b.timestamp > (uint64_t)time(NULL) + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT)
|
||||||
{
|
{
|
||||||
MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours");
|
MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than local time + 2 hours");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1042,6 +1042,21 @@ namespace cryptonote
|
||||||
*/
|
*/
|
||||||
void flush_invalid_blocks();
|
void flush_invalid_blocks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get the "adjusted time"
|
||||||
|
*
|
||||||
|
* Computes the median timestamp of the previous 60 blocks, projects it
|
||||||
|
* onto the current block to get an 'adjusted median time' which approximates
|
||||||
|
* what the current block's timestamp should be. Also projects the previous
|
||||||
|
* block's timestamp to estimate the current block's timestamp.
|
||||||
|
*
|
||||||
|
* Returns the minimum of the two projections, or the current local time on
|
||||||
|
* the machine if less than 60 blocks are available.
|
||||||
|
*
|
||||||
|
* @return current time approximated from chain data
|
||||||
|
*/
|
||||||
|
uint64_t get_adjusted_time(uint64_t height) const;
|
||||||
|
|
||||||
#ifndef IN_UNIT_TESTS
|
#ifndef IN_UNIT_TESTS
|
||||||
private:
|
private:
|
||||||
#endif
|
#endif
|
||||||
|
@ -1182,10 +1197,11 @@ namespace cryptonote
|
||||||
* @param output_keys return-by-reference the public keys of the outputs in the input set
|
* @param output_keys return-by-reference the public keys of the outputs in the input set
|
||||||
* @param rct_signatures the ringCT signatures, which are only valid if tx version > 1
|
* @param rct_signatures the ringCT signatures, which are only valid if tx version > 1
|
||||||
* @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set
|
* @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set
|
||||||
|
* @param hf_version the consensus rules version to use
|
||||||
*
|
*
|
||||||
* @return false if any output is not yet unlocked, or is missing, otherwise true
|
* @return false if any output is not yet unlocked, or is missing, otherwise true
|
||||||
*/
|
*/
|
||||||
bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height) const;
|
bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height, uint8_t hf_version) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief validate a transaction's inputs and their keys
|
* @brief validate a transaction's inputs and their keys
|
||||||
|
@ -1373,10 +1389,11 @@ namespace cryptonote
|
||||||
* unlock_time is either a block index or a unix time.
|
* unlock_time is either a block index or a unix time.
|
||||||
*
|
*
|
||||||
* @param unlock_time the unlock parameter (height or time)
|
* @param unlock_time the unlock parameter (height or time)
|
||||||
|
* @param hf_version the consensus rules version to use
|
||||||
*
|
*
|
||||||
* @return true if spendable, otherwise false
|
* @return true if spendable, otherwise false
|
||||||
*/
|
*/
|
||||||
bool is_tx_spendtime_unlocked(uint64_t unlock_time) const;
|
bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint8_t hf_version) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief stores an invalid block in a separate container
|
* @brief stores an invalid block in a separate container
|
||||||
|
@ -1437,16 +1454,6 @@ namespace cryptonote
|
||||||
bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b, uint64_t& median_ts) const;
|
bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b, uint64_t& median_ts) const;
|
||||||
bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const { uint64_t median_ts; return check_block_timestamp(timestamps, b, median_ts); }
|
bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const { uint64_t median_ts; return check_block_timestamp(timestamps, b, median_ts); }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief get the "adjusted time"
|
|
||||||
*
|
|
||||||
* Currently this simply returns the current time according to the
|
|
||||||
* user's machine.
|
|
||||||
*
|
|
||||||
* @return the current time
|
|
||||||
*/
|
|
||||||
uint64_t get_adjusted_time() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief finish an alternate chain's timestamp window from the main chain
|
* @brief finish an alternate chain's timestamp window from the main chain
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue