Use recent block times for unlock time#6745
Conversation
There was a problem hiding this comment.
Is removing the static cast for time(NULL) ok/intended here?
Also, according to the other changes in this PR get_adjusted_time() will return the median of the last 60 blocks' timestamps.. which should be 30 blocks ago (60 minutes ago) on average. Not the current time. Am I missing something or this broken?
One way to fix this is to estimate the current time from the median block's time.
//approximate the time (in UTC) of the current block based on the block that should be positioned at 1/2 of the blockchain check window, and how often blocks are produced on average
//using the median means the 'height' block author can't manipulate their timestamp to significantly affect the return value from this function
... get_adjusted_time(m_db->height()) + static_cast<uint64_t>((BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW/2) * (get_ideal_hard_fork_version(height) < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2)) ...
Honestly it's a bit messy, so maybe add a function. The terminology is really mixed up here since the behavior of get_adjusted_time() should really be in a function called get_timestamp_check_median_time(), and get_adjusted_time() should return the approximate time of the current block and call get_timestamp_check_median_time() internally.
There was a problem hiding this comment.
Can't recall. It seems pointless anyway.
The whole point is to start using blockchain timestamps instead of local time. This is intended. A 30 minute delay is fine, since this is typically used to lock things long term, not for a few minutes I think ? Define broken. Adding an offset would need changing some tests IIRC, and means you might unlck too early instead of too late. Why do you think this may be needed ?
There was a problem hiding this comment.
By 'broken' I meant 'unintended consequences'. It sounds like the changes are intentional.
"A 30 minute delay is fine, since this is typically used to lock things long term, not for a few minutes I think ?" Presumably if someone is locking a transaction with UTC time, they are trying to be more precise than a block height lock could provide. As the PR stands, users will have to wait an extra 60mins on average for their outputs to be unlocked (relative to the unlock time).
It sounds like you are concerned about future times being used for timelock checks. What about correcting the offset by doing a minimum of the current block's block time and the median block's time + 60mins? If blocks are accelerating then the current block's time will be more accurate than the median + 60, if the current block has a future time then the median + 60 should be closer to the real time, and if blocks are decelerating then authors will have to wait a few extra minutes to spend their outputs.
Note that even without the offset someone who can manipulate the median could push it into the future up to +1hr (since block times are limited by local_time + 2hr according to check_block_timestamp()). Adding the corrected offset allows timelocked tx to be spent up to 2hrs early in the worst case, while being much closer to +/- 10mins in the typical case.
There was a problem hiding this comment.
The concern goes in both directions, faster and slower blocks, meaning both block timestamps into the future and the past. I'd argue a window of +-60 minutes is the time precission that we can provide and that it is good enough. If you lock a transaction's outputs with unix time exactly a year into the future, you get a guaranteed precision of +-60 minutes, while if you use blocks, the precision is unknown and could vary days.
Having the ability to push the median significantly would probably be at the limit of our security boundaries, since it is miners that are pushing it. I have not done any statistics on this though.
There was a problem hiding this comment.
Sorry, rather than 'current block' for corrected offset it should be 'previous block + 2mins' since the current block may be under construction and we don't want to use local time.
Well, +/- 60mins might be ok, but I'm wondering why not push for even more precision if it's possible?
There was a problem hiding this comment.
I spent a bit of time going through some of the potential problems with koe's proposal and think it is safe enough to go with it. It does introduce some extra complexity though and detecting a few of the corner cases has been challenging. The proposal does allow a miner to block inclusion of a time locked transaction into the next block by setting a low timestamp of his own mined block. In the current protocol, there are no incentives or rewards gained by doing this. Given this added complexity I would like to get a bit more review here.
There was a problem hiding this comment.
To get the adjusted time I think it should be like this:
// 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 project 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 = m_db->get_block_timestamp(height - 1) + 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);
Note: on the odd looking +1 in median_ts... imagine:
- window = 2, blocks are indexed b0, b1, b2; block b2 is being validated; median of window is at timestamp (b0 + b1)/2 = 0.5b; adding offset of window/2 means adjusted timestamp would be 1.5b so we need to add another 0.5b -> 0.5b + (window + 1)/2 = 2b
- window = 3, blocks are indexed b0, b1, b2, b3; block b3 is being validated; median of window is at timestamp 1b; adding offset of window/2 means adjusted timestamp would be 2.5b so we need to add another 0.5b -> 1b + (window + 1)/2 = 3b
There was a problem hiding this comment.
I assume adjusted_current_block_ts and previous_block_ts are the same variable here, right ?
There was a problem hiding this comment.
Pushed with that change.
There was a problem hiding this comment.
| 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"); | |
| 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 local time + 2 hours"); |
There was a problem hiding this comment.
Why was this declaration moved up 400 lines? New spot seems less appropriate based on other declarations nearby.
There was a problem hiding this comment.
To make it public.
There was a problem hiding this comment.
I guess I could add public: and private: and keep it where it was though. Seemed a bit meh but it minimizes the diff...
There was a problem hiding this comment.
Ah if it's being made public that makes sense.
There was a problem hiding this comment.
Looks like terminology confusion again, since 'adjusted_time' has been changed to mean 'median time of last 60 blocks'. Which makes the behavior here change.
There was a problem hiding this comment.
Looks like it was already broken. I'm not sure why it's adding that define. The threshold really should be adjusted_time. If you can't think of why the define gets added either, I'll remove.
There was a problem hiding this comment.
Not sure what you are referring to. Define?
There was a problem hiding this comment.
I think we should rename it to locktime_median_time_past or something along those lines to avoid confusion.
There was a problem hiding this comment.
By define, I meant adding CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 etc. I don't see what purpose this has.
There was a problem hiding this comment.
Is this a Chesterton's fence? It does feel like an idea that didn't get fully fleshed out. It has been there ever since the original fork.
There was a problem hiding this comment.
On second thought, it doesn't hurt anything either. So as you imply, safer to keep it in at least for now.
There was a problem hiding this comment.
The name seems clear to me. It's getting the adjusted time from the daemon, not the current time.
There was a problem hiding this comment.
Is this 3-parameter overload of check_block_timestamp() being deprecated? If so, might be nice to remove it completely.
There was a problem hiding this comment.
It looks like it's still used.
There was a problem hiding this comment.
Looks like the same as the one above. I think I might be missing your point though, since the point of the patch is to change the behaviour. So can you expand on whether you think something else (than the addition) is wrong ?
There was a problem hiding this comment.
Sorry, just pointing out that every use of 'adjusted_time' is really '60 mins ago on average'. Since they are all replacing 'current time' the behavior changes.
There was a problem hiding this comment.
Why was const removed from all of these functions?
There was a problem hiding this comment.
Because they now call RPC.
|
Is the unbound submodule update intended? |
|
No. Not sure how it ended up here, thanks. |
|
Ah, I found the behaviour change, a place is changing this without checking for v13. Will fix. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
Fyi, has conflicts with |
6ee9003 to
edf9e24
Compare
|
Now with the expected extra time. I'll rebase later since that'll pollute the diff massively. |
|
I do not have the space to sync another chain here, it would be nice if someone could try a full sync to make sure this does not break historical blocks :) |
|
I'm wondering whether this change can lock an ouput after it was unlocked, if the adjusted time goes back in time (ie, a new block has an earlier ts than the one 60 block ago). That could be annoying. |
|
If the last block is re-orged then sure, this can happen. However this is even true if you take just the median block times without additional modification. As long as the time used for the validation is relative to whatever the state is when the transaction is first included in a block, a change of the time into the past in a later block does not matter. |
edf9e24 to
7f0b685
Compare
There was a problem hiding this comment.
This comment is out of date, otherwise everything looks good.
/**
* @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
*/
7f0b685 to
b09e34e
Compare
b695126 to
c903435
Compare
There was a problem hiding this comment.
| uint64_t adjusted_current_block_ts = m_db->get_block_timestamp(height - 1) + DIFFICULTY_TARGET_V2; | |
| uint64_t adjusted_current_block_ts = timestamps.back() + DIFFICULTY_TARGET_V2; |
Based on a patch by TheCharlatan <seb.kung@gmail.com>
c903435 to
80e535c
Compare
From an original patch by TheCharlatan.