-
Notifications
You must be signed in to change notification settings - Fork 781
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
Use checked arithmetic in types and state proc #1009
Conversation
Still WIP, I've noticed a few things in the diff that need correcting |
972de7f
to
3e4dc7a
Compare
Ok, ready for review. I'm just going to run some benchmarks of state processing to assess the impact on performance. |
Benchmarks have revealed no statistically significant deviation from
Worst-case blocks, i7-8550U, minimal spec for the 32K validators, mainnet spec for the 300K, Weirdly the benchmark is faster for the larger blocks, and the bulk signature verification is a lot slower, I don't know what's going on there but we can investigate that separately. |
@@ -38,7 +37,7 @@ impl Slot { | |||
} | |||
|
|||
pub fn epoch(self, slots_per_epoch: u64) -> Epoch { | |||
Epoch::from(self.0 / slots_per_epoch) | |||
Epoch::from(self.0) / Epoch::from(slots_per_epoch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe use checked_div
or safe_div
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah, I figured this was quite innocuous here, because slots_per_epoch
is unlikely to ever be 0, and it would at least panic rather than wrapping in a strange way. I'll see how much flow-on effect returning a Result
would have though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we do an explicit panic
if propagating the Result
is too onerous (I suspect this will be the case). That way we get the panic in release too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used Epoch
division here because it will explicitly panic:
lighthouse/eth2/types/src/slot_epoch_macros.rs
Lines 108 to 114 in 3e4dc7a
fn div(self, rhs: $other) -> $main { | |
let rhs: u64 = rhs.into(); | |
$main::from( | |
self.0 | |
.checked_div(rhs) | |
.expect("Cannot divide by zero-valued Slot/Epoch"), | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to leave this as-is, if that's OK with everyone.. I count 60 uses of .epoch()
all throughout different crates, almost always with T::slots_per_epoch()
as the argument, so I don't think it's worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine by me. Setting SLOTS_PER_EPOCH
is not reasonable and there's plenty of other panics we could cause by setting "constants" to unreasonable values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also not really "user supplied input". I'd say it's "programmer supplied input".
@@ -274,22 +274,20 @@ impl MerkleHasher { | |||
loop { | |||
if let Some(root) = self.root { | |||
break Ok(root); | |||
} else if let Some(node) = self.half_nodes.last() { | |||
let right_child = node.id * 2 + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mul
and add
can be checked here since we return Result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, we could. This refactor was just leftover from me trying to fix generic clippy lints project-wide (which I ended up disabling for now), and this crate isn't being linted currently. I agree we could expand the usage of checked arithmetic though, so I've made an issue to track that: #1013
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great effort! This would have been tedious but it's really great to see this overflow behavior locked down!
I just have a couple of questions, nothing major at all.
eth2/state_processing/src/per_block_processing/block_signature_verifier.rs
Show resolved
Hide resolved
eth2/state_processing/src/per_epoch_processing/process_slashings.rs
Outdated
Show resolved
Hide resolved
@@ -38,7 +37,7 @@ impl Slot { | |||
} | |||
|
|||
pub fn epoch(self, slots_per_epoch: u64) -> Epoch { | |||
Epoch::from(self.0 / slots_per_epoch) | |||
Epoch::from(self.0) / Epoch::from(slots_per_epoch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we do an explicit panic
if propagating the Result
is too onerous (I suspect this will be the case). That way we get the panic in release too.
Feel free to squerge when the tests pass :) |
Proposed Changes
types
andstate_processing
crates, so that we're forced to be explicit about what sort of arithmetic we want, and can catch unwanted overflow and consider it an invalid state transition (as per Clarity onuint
overflow ethereum/consensus-specs#1701)checked_
functions from the standard library. To make things more ergonomic, I introduced asafe_arith
crate which returnsResult
s from the checked operations instead ofOption
s.