Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reintroduce timestamp checks #1724

Closed
wants to merge 4 commits into from
Closed

Reintroduce timestamp checks #1724

wants to merge 4 commits into from

Conversation

herr-seppia
Copy link
Member

@herr-seppia herr-seppia commented May 10, 2024

Added the following checks:

  • Timestamp of block with height=H must be greater or equal to timestamp of block with height=H-1
  • Timestamp of any block must be lower or equal than current OS timestamp
  • Timestamp of block with height=H generated at iteration=I must be lower or equal to the timestamp of block with height=H-1 plus I*MAX_ITER_TIMEOUT 1

See also #1649
See also dusk-network/dips#13


EDIT: This PR has to be repurposed to address #1494

Footnotes

  1. This check is enforced only if the iteration I is not relaxed -> I < RELAX_ITERATION_THRESHOLD

@herr-seppia herr-seppia marked this pull request as ready for review June 3, 2024 13:39
@fed-franz
Copy link
Contributor

As far as I remember, we decided to reject the DIP proposal. And the #1649 is to avoid meaningless timestamps (e.g., as stated in the issue: avoid blocks from one day in the future) not to introduce strict timestamp conditions on blocks.

Copy link
Contributor

@fed-franz fed-franz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks are very strict. Didn't we agree on leaving some margin of error (i.e. the slippage)?

Also, we should probably make sure all nodes use NTP, or are synced in some way, before enforcing these checks on mainnet (see dusk-network/dips#12).

}

if candidate_block.timestamp < self.prev_header.timestamp {
anyhow::bail!("invalid timestamp");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
anyhow::bail!("invalid timestamp");
anyhow::bail!("invalid timestamp: lower than previous block");

}

if candidate_block.timestamp > get_current_timestamp() {
anyhow::bail!("invalid future timestamp");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
anyhow::bail!("invalid future timestamp");
anyhow::bail!("invalid timestamp: candidate from the future");

candidate_block.timestamp - self.prev_header.timestamp;
if current_delta > max_delta {
anyhow::bail!(
"invalid timestamp, delta: {current_delta}/{max_delta}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"invalid timestamp, delta: {current_delta}/{max_delta}"
"invalid timestamp: candidate is too late (delta: {current_delta}/{max_delta})"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check against the current timestamp (or better, the time at which the candidate was received), instead of the timestamp in the candidate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? If I check the current timestamp the candidate timestamp can be invalid (checks for current timestamp/iteration are natives performed by the iteration timeout mechanism).
Timestamp of candidate should always be lower than the receiving timestamp.

Furthermore this check is performed for any block, not only for live votes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning was that a candidate that is maliciously sent at a later time could still pass this check by setting an appropriate timestamp. But I guess in non-relaxed iterations, the candidate would be equally rejected due to step timeout.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still, I'm not sure about the benefit of this check on Candidate messages...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this check while accepting the candidate, will reject the candidate itself and let the provisioner to not vote (even if the provisioner has some iteration delay)

Copy link
Contributor

@goshawk-3 goshawk-3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my benefit, can someone explain how this PR is addressing consensus forks issues? We used to have consensus forks before removing the block timestamp too.

if msg.header.prev_block_hash != p.candidate.header().prev_block_hash {
return Err(ConsensusError::InvalidBlockHash);
}

if p.candidate.header().timestamp > get_current_timestamp() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can be more error-tolerant here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you suggest as a fair more tolerant check?

Having a more tolerant check here will introduce some delay effect in the chain.
Eg: if we accept block 5 mins from the future, that mean that the next block cannot be produced for the next 5 mins due to sequential check

_committee: &Committee,
) -> Result<HandleMsgOutput, ConsensusError> {
// store candidate block
let p = Self::unwrap_msg(&msg)?;

if p.candidate.header().timestamp < ru.timestamp() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any intention by putting this check here instead of ProposalHandler::verify(? If so, let's have it as a comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be the same, since both will discard the block. I'm not strongly opinionated on this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ProposalHandler::verify( considers it valid (returns true) the message is rebroadcast. (if that is the intention then it's fine)

anyhow::bail!("invalid timestamp");
}

if candidate_block.iteration < RELAX_ITERATION_THRESHOLD {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have a candidate above RELAX_ITERATION_THRESHOLD, shouldn't we still have some max_delta?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relaxed iterations have no time limits, they move on to the next step only when the current step reaches a result.
In the case of Proposal, the step waits indefinitely for a Candidate block.
So there shouldn't be any timestamp check beyond the condition of being subsequent to the previous block

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_delta is depending to the iteration

Indeed is MAX_ITERATION_TIMEOUT*ITERATION

@goshawk-3
Copy link
Contributor

These checks are very strict. Didn't we agree on leaving some margin of error (i.e. the slippage)?

Also, we should probably make sure all nodes use NTP, or are synced in some way, before enforcing these checks on mainnet (see dusk-network/dips#12).

I agree with that.
If we intend to merge this PR into master before enabling NTP, I'd like to request a long-run devnet test where we ensure consensus is stable without NTP enabled.

@herr-seppia
Copy link
Member Author

Also, we should probably make sure all nodes use NTP

I agree with that. If we intend to merge this PR into master before enabling NTP

How can rusk binary be sure that NTPd is enabled on the host OS?

@fed-franz
Copy link
Contributor

For my benefit, can someone explain how this PR is addressing consensus forks issues? We used to have consensus forks before removing the block timestamp too.

The original intent of (re)introducing some timestamp check on consensus messages was to deal with the "Nothing at stake" problem, where we needed to determine some temporal order of actions (of the same provisioner) on parallel branches.
While this goal has been dismissed, we decided to add some constraints to candidate timestamps as a way of mitigate cases where the candidate is published very late compared to when the iteration occurs.

However, as I commented, I'm not sure about the actual benefit of these checks.
If @herr-seppia has it clear maybe leave a comment about that. Otherwise we might want to have a second round of discussion on this

@herr-seppia
Copy link
Member Author

herr-seppia commented Jun 4, 2024

For my benefit, can someone explain how this PR is addressing consensus forks issues?

This PR is not completely addressing that, but it should (IMO) mitigate it.
Right now "forks" happen when delayed provisioner(s) vote for block after a while (2-3 minutes)

It will mitigate it because delayed provisioners will not vote for candidates anymore if the MAX_DELAY has been reached.

We used to have consensus forks before removing the block timestamp too.

Checks we used to have in the past were not addressing this, they were just checking if a block timestamp was too much from the future and then edit it to be no more than 5 mins

@fed-franz
Copy link
Contributor

This PR is not completely addressing that, but it should (IMO) mitigate it. Right now "forks" happen when delayed provisioner(s) vote for block after a while (2-3 minutes)

It will mitigate it because delayed provisioners will not vote for candidates anymore if the MAX_DELAY has been reached.

The problem is that the check is done on the timestamp included in the candidate. So, if the candidate was produced in time, late voters will still vote for it. That's why I was suggesting to compare against the current timestamp instead of the candidate's one.
Alternatively, like stated in the original proposal, we should check the vote's timestamp against the candidate's one.

@autholykos
Copy link
Member

Solves #1494

@fed-franz fed-franz linked an issue Jun 11, 2024 that may be closed by this pull request
@herr-seppia
Copy link
Member Author

Closing as obsolete

@fed-franz
Copy link
Contributor

Can we delete the branch?

@herr-seppia herr-seppia deleted the timestamp_checks branch July 11, 2024 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

consensus: Increase block time
4 participants