Remove saturating arith from state_processing (#1644)
## Issue Addressed Resolves #1100 ## Proposed Changes * Implement the `SafeArith` trait for `Slot` and `Epoch`, so that methods like `safe_add` become available. * Tweak the `SafeArith` trait to allow a different `Rhs` type (analagous to `std::ops::Add`, etc). * Add a `legacy-arith` feature to `types` and `state_processing` that conditionally enables implementations of the `std` ops with saturating semantics. * Check compilation of `types` and `state_processing` _without_ `legacy-arith` on CI, thus guaranteeing that they only use the `SafeArith` primitives 🎉 ## Additional Info The `legacy-arith` feature gets turned on by all higher-level crates that depend on `state_processing` or `types`, thus allowing the beacon chain, networking, and other components to continue to rely on the availability of ops like `+`, `-`, `*`, etc. **This is a consensus-breaking change**, but brings us in line with the spec, and our incompatibilities shouldn't have been reachable with any valid configuration of Eth2 parameters.
This commit is contained in:
parent
28b6d921c6
commit
3412a3ec54
8
.github/workflows/test-suite.yml
vendored
8
.github/workflows/test-suite.yml
vendored
@ -115,6 +115,14 @@ jobs:
|
|||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Typecheck benchmark code without running it
|
- name: Typecheck benchmark code without running it
|
||||||
run: make check-benches
|
run: make check-benches
|
||||||
|
check-consensus:
|
||||||
|
name: check-consensus
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: cargo-fmt
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Typecheck consensus code in strict mode
|
||||||
|
run: make check-consensus
|
||||||
clippy:
|
clippy:
|
||||||
name: clippy
|
name: clippy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
4
Makefile
4
Makefile
@ -93,6 +93,10 @@ cargo-fmt:
|
|||||||
check-benches:
|
check-benches:
|
||||||
cargo check --all --benches
|
cargo check --all --benches
|
||||||
|
|
||||||
|
# Typechecks consensus code *without* allowing deprecated legacy arithmetic
|
||||||
|
check-consensus:
|
||||||
|
cargo check --manifest-path=consensus/state_processing/Cargo.toml --no-default-features
|
||||||
|
|
||||||
# Runs only the ef-test vectors.
|
# Runs only the ef-test vectors.
|
||||||
run-ef-tests:
|
run-ef-tests:
|
||||||
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests"
|
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests"
|
||||||
|
@ -28,24 +28,24 @@ macro_rules! assign_method {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Trait providing safe arithmetic operations for built-in types.
|
/// Trait providing safe arithmetic operations for built-in types.
|
||||||
pub trait SafeArith: Sized + Copy {
|
pub trait SafeArith<Rhs = Self>: Sized + Copy {
|
||||||
const ZERO: Self;
|
const ZERO: Self;
|
||||||
const ONE: Self;
|
const ONE: Self;
|
||||||
|
|
||||||
/// Safe variant of `+` that guards against overflow.
|
/// Safe variant of `+` that guards against overflow.
|
||||||
fn safe_add(&self, other: Self) -> Result<Self>;
|
fn safe_add(&self, other: Rhs) -> Result<Self>;
|
||||||
|
|
||||||
/// Safe variant of `-` that guards against overflow.
|
/// Safe variant of `-` that guards against overflow.
|
||||||
fn safe_sub(&self, other: Self) -> Result<Self>;
|
fn safe_sub(&self, other: Rhs) -> Result<Self>;
|
||||||
|
|
||||||
/// Safe variant of `*` that guards against overflow.
|
/// Safe variant of `*` that guards against overflow.
|
||||||
fn safe_mul(&self, other: Self) -> Result<Self>;
|
fn safe_mul(&self, other: Rhs) -> Result<Self>;
|
||||||
|
|
||||||
/// Safe variant of `/` that guards against division by 0.
|
/// Safe variant of `/` that guards against division by 0.
|
||||||
fn safe_div(&self, other: Self) -> Result<Self>;
|
fn safe_div(&self, other: Rhs) -> Result<Self>;
|
||||||
|
|
||||||
/// Safe variant of `%` that guards against division by 0.
|
/// Safe variant of `%` that guards against division by 0.
|
||||||
fn safe_rem(&self, other: Self) -> Result<Self>;
|
fn safe_rem(&self, other: Rhs) -> Result<Self>;
|
||||||
|
|
||||||
/// Safe variant of `<<` that guards against overflow.
|
/// Safe variant of `<<` that guards against overflow.
|
||||||
fn safe_shl(&self, other: u32) -> Result<Self>;
|
fn safe_shl(&self, other: u32) -> Result<Self>;
|
||||||
@ -53,18 +53,13 @@ pub trait SafeArith: Sized + Copy {
|
|||||||
/// Safe variant of `>>` that guards against overflow.
|
/// Safe variant of `>>` that guards against overflow.
|
||||||
fn safe_shr(&self, other: u32) -> Result<Self>;
|
fn safe_shr(&self, other: u32) -> Result<Self>;
|
||||||
|
|
||||||
assign_method!(safe_add_assign, safe_add, "+=");
|
assign_method!(safe_add_assign, safe_add, Rhs, "+=");
|
||||||
assign_method!(safe_sub_assign, safe_sub, "-=");
|
assign_method!(safe_sub_assign, safe_sub, Rhs, "-=");
|
||||||
assign_method!(safe_mul_assign, safe_mul, "*=");
|
assign_method!(safe_mul_assign, safe_mul, Rhs, "*=");
|
||||||
assign_method!(safe_div_assign, safe_div, "/=");
|
assign_method!(safe_div_assign, safe_div, Rhs, "/=");
|
||||||
assign_method!(safe_rem_assign, safe_rem, "%=");
|
assign_method!(safe_rem_assign, safe_rem, Rhs, "%=");
|
||||||
assign_method!(safe_shl_assign, safe_shl, u32, "<<=");
|
assign_method!(safe_shl_assign, safe_shl, u32, "<<=");
|
||||||
assign_method!(safe_shr_assign, safe_shr, u32, ">>=");
|
assign_method!(safe_shr_assign, safe_shr, u32, ">>=");
|
||||||
|
|
||||||
/// Mutate `self` by adding 1, erroring on overflow.
|
|
||||||
fn increment(&mut self) -> Result<()> {
|
|
||||||
self.safe_add_assign(Self::ONE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_safe_arith {
|
macro_rules! impl_safe_arith {
|
||||||
@ -136,8 +131,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn mutate() {
|
fn mutate() {
|
||||||
let mut x = 0u8;
|
let mut x = 0u8;
|
||||||
x.increment().unwrap();
|
x.safe_add_assign(2).unwrap();
|
||||||
x.increment().unwrap();
|
|
||||||
assert_eq!(x, 2);
|
assert_eq!(x, 2);
|
||||||
x.safe_sub_assign(1).unwrap();
|
x.safe_sub_assign(1).unwrap();
|
||||||
assert_eq!(x, 1);
|
assert_eq!(x, 1);
|
||||||
|
@ -27,14 +27,16 @@ log = "0.4.8"
|
|||||||
safe_arith = { path = "../safe_arith" }
|
safe_arith = { path = "../safe_arith" }
|
||||||
tree_hash = "0.1.0"
|
tree_hash = "0.1.0"
|
||||||
tree_hash_derive = "0.2.0"
|
tree_hash_derive = "0.2.0"
|
||||||
types = { path = "../types" }
|
types = { path = "../types", default-features = false }
|
||||||
rayon = "1.3.0"
|
rayon = "1.3.0"
|
||||||
eth2_hashing = "0.1.0"
|
eth2_hashing = "0.1.0"
|
||||||
int_to_bytes = { path = "../int_to_bytes" }
|
int_to_bytes = { path = "../int_to_bytes" }
|
||||||
arbitrary = { version = "0.4.4", features = ["derive"], optional = true }
|
arbitrary = { version = "0.4.4", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["legacy-arith"]
|
||||||
fake_crypto = ["bls/fake_crypto"]
|
fake_crypto = ["bls/fake_crypto"]
|
||||||
|
legacy-arith = ["types/legacy-arith"]
|
||||||
arbitrary-fuzz = [
|
arbitrary-fuzz = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"types/arbitrary-fuzz",
|
"types/arbitrary-fuzz",
|
||||||
|
@ -47,7 +47,7 @@ impl DepositDataTree {
|
|||||||
/// Add a deposit to the merkle tree.
|
/// Add a deposit to the merkle tree.
|
||||||
pub fn push_leaf(&mut self, leaf: Hash256) -> Result<(), MerkleTreeError> {
|
pub fn push_leaf(&mut self, leaf: Hash256) -> Result<(), MerkleTreeError> {
|
||||||
self.tree.push_leaf(leaf, self.depth)?;
|
self.tree.push_leaf(leaf, self.depth)?;
|
||||||
self.mix_in_length.increment()?;
|
self.mix_in_length.safe_add_assign(1)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use safe_arith::SafeArith;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use types::{BeaconStateError as Error, *};
|
use types::{BeaconStateError as Error, *};
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ pub fn initiate_validator_exit<T: EthSpec>(
|
|||||||
state.exit_cache.build(&state.validators, spec)?;
|
state.exit_cache.build(&state.validators, spec)?;
|
||||||
|
|
||||||
// Compute exit queue epoch
|
// Compute exit queue epoch
|
||||||
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec);
|
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec)?;
|
||||||
let mut exit_queue_epoch = state
|
let mut exit_queue_epoch = state
|
||||||
.exit_cache
|
.exit_cache
|
||||||
.max_epoch()?
|
.max_epoch()?
|
||||||
@ -30,13 +31,13 @@ pub fn initiate_validator_exit<T: EthSpec>(
|
|||||||
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch)?;
|
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch)?;
|
||||||
|
|
||||||
if exit_queue_churn >= state.get_churn_limit(spec)? {
|
if exit_queue_churn >= state.get_churn_limit(spec)? {
|
||||||
exit_queue_epoch += 1;
|
exit_queue_epoch.safe_add_assign(1)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.exit_cache.record_validator_exit(exit_queue_epoch)?;
|
state.exit_cache.record_validator_exit(exit_queue_epoch)?;
|
||||||
state.validators[index].exit_epoch = exit_queue_epoch;
|
state.validators[index].exit_epoch = exit_queue_epoch;
|
||||||
state.validators[index].withdrawable_epoch =
|
state.validators[index].withdrawable_epoch =
|
||||||
exit_queue_epoch + spec.min_validator_withdrawability_delay;
|
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ pub fn slash_validator<T: EthSpec>(
|
|||||||
state.validators[slashed_index].slashed = true;
|
state.validators[slashed_index].slashed = true;
|
||||||
state.validators[slashed_index].withdrawable_epoch = cmp::max(
|
state.validators[slashed_index].withdrawable_epoch = cmp::max(
|
||||||
state.validators[slashed_index].withdrawable_epoch,
|
state.validators[slashed_index].withdrawable_epoch,
|
||||||
epoch + Epoch::from(T::EpochsPerSlashingsVector::to_u64()),
|
epoch.safe_add(T::EpochsPerSlashingsVector::to_u64())?,
|
||||||
);
|
);
|
||||||
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
|
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
|
||||||
state.set_slashings(
|
state.set_slashings(
|
||||||
|
@ -368,7 +368,7 @@ pub fn process_attestations<T: EthSpec>(
|
|||||||
let pending_attestation = PendingAttestation {
|
let pending_attestation = PendingAttestation {
|
||||||
aggregation_bits: attestation.aggregation_bits.clone(),
|
aggregation_bits: attestation.aggregation_bits.clone(),
|
||||||
data: attestation.data.clone(),
|
data: attestation.data.clone(),
|
||||||
inclusion_delay: (state.slot - attestation.data.slot).as_u64(),
|
inclusion_delay: state.slot.safe_sub(attestation.data.slot)?.as_u64(),
|
||||||
proposer_index,
|
proposer_index,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -444,7 +444,7 @@ pub fn process_deposit<T: EthSpec>(
|
|||||||
.map_err(|e| e.into_with_index(deposit_index))?;
|
.map_err(|e| e.into_with_index(deposit_index))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.eth1_deposit_index.increment()?;
|
state.eth1_deposit_index.safe_add_assign(1)?;
|
||||||
|
|
||||||
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
||||||
// already exists in the beacon_state.
|
// already exists in the beacon_state.
|
||||||
|
@ -2,6 +2,7 @@ use super::errors::{AttestationInvalid as Invalid, BlockOperationError};
|
|||||||
use super::VerifySignatures;
|
use super::VerifySignatures;
|
||||||
use crate::common::get_indexed_attestation;
|
use crate::common::get_indexed_attestation;
|
||||||
use crate::per_block_processing::is_valid_indexed_attestation;
|
use crate::per_block_processing::is_valid_indexed_attestation;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
|
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
|
||||||
@ -25,7 +26,7 @@ pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
|
|||||||
let data = &attestation.data;
|
let data = &attestation.data;
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
data.slot.safe_add(spec.min_attestation_inclusion_delay)? <= state.slot,
|
||||||
Invalid::IncludedTooEarly {
|
Invalid::IncludedTooEarly {
|
||||||
state: state.slot,
|
state: state.slot,
|
||||||
delay: spec.min_attestation_inclusion_delay,
|
delay: spec.min_attestation_inclusion_delay,
|
||||||
@ -33,7 +34,7 @@ pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
state.slot <= data.slot + T::slots_per_epoch(),
|
state.slot <= data.slot.safe_add(T::slots_per_epoch())?,
|
||||||
Invalid::IncludedTooLate {
|
Invalid::IncludedTooLate {
|
||||||
state: state.slot,
|
state: state.slot,
|
||||||
attestation: data.slot,
|
attestation: data.slot,
|
||||||
|
@ -3,6 +3,7 @@ use crate::per_block_processing::{
|
|||||||
signature_sets::{exit_signature_set, get_pubkey_from_state},
|
signature_sets::{exit_signature_set, get_pubkey_from_state},
|
||||||
VerifySignatures,
|
VerifySignatures,
|
||||||
};
|
};
|
||||||
|
use safe_arith::SafeArith;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, BlockOperationError<ExitInvalid>>;
|
type Result<T> = std::result::Result<T, BlockOperationError<ExitInvalid>>;
|
||||||
@ -77,11 +78,14 @@ fn verify_exit_parametric<T: EthSpec>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Verify the validator has been active long enough.
|
// Verify the validator has been active long enough.
|
||||||
|
let earliest_exit_epoch = validator
|
||||||
|
.activation_epoch
|
||||||
|
.safe_add(spec.shard_committee_period)?;
|
||||||
verify!(
|
verify!(
|
||||||
state.current_epoch() >= validator.activation_epoch + spec.shard_committee_period,
|
state.current_epoch() >= earliest_exit_epoch,
|
||||||
ExitInvalid::TooYoungToExit {
|
ExitInvalid::TooYoungToExit {
|
||||||
current_epoch: state.current_epoch(),
|
current_epoch: state.current_epoch(),
|
||||||
earliest_exit_epoch: validator.activation_epoch + spec.shard_committee_period,
|
earliest_exit_epoch,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ pub fn process_justification_and_finalization<T: EthSpec>(
|
|||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
total_balances: &TotalBalances,
|
total_balances: &TotalBalances,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if state.current_epoch() <= T::genesis_epoch() + 1 {
|
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,25 +126,25 @@ pub fn process_justification_and_finalization<T: EthSpec>(
|
|||||||
|
|
||||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
||||||
if (1..4).all(|i| bits.get(i).unwrap_or(false))
|
if (1..4).all(|i| bits.get(i).unwrap_or(false))
|
||||||
&& old_previous_justified_checkpoint.epoch + 3 == current_epoch
|
&& old_previous_justified_checkpoint.epoch.safe_add(3)? == current_epoch
|
||||||
{
|
{
|
||||||
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
||||||
}
|
}
|
||||||
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
||||||
else if (1..3).all(|i| bits.get(i).unwrap_or(false))
|
else if (1..3).all(|i| bits.get(i).unwrap_or(false))
|
||||||
&& old_previous_justified_checkpoint.epoch + 2 == current_epoch
|
&& old_previous_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||||
{
|
{
|
||||||
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
||||||
}
|
}
|
||||||
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
|
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
|
||||||
if (0..3).all(|i| bits.get(i).unwrap_or(false))
|
if (0..3).all(|i| bits.get(i).unwrap_or(false))
|
||||||
&& old_current_justified_checkpoint.epoch + 2 == current_epoch
|
&& old_current_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||||
{
|
{
|
||||||
state.finalized_checkpoint = old_current_justified_checkpoint;
|
state.finalized_checkpoint = old_current_justified_checkpoint;
|
||||||
}
|
}
|
||||||
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
||||||
else if (0..2).all(|i| bits.get(i).unwrap_or(false))
|
else if (0..2).all(|i| bits.get(i).unwrap_or(false))
|
||||||
&& old_current_justified_checkpoint.epoch + 1 == current_epoch
|
&& old_current_justified_checkpoint.epoch.safe_add(1)? == current_epoch
|
||||||
{
|
{
|
||||||
state.finalized_checkpoint = old_current_justified_checkpoint;
|
state.finalized_checkpoint = old_current_justified_checkpoint;
|
||||||
}
|
}
|
||||||
@ -160,10 +160,15 @@ pub fn process_final_updates<T: EthSpec>(
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch();
|
let current_epoch = state.current_epoch();
|
||||||
let next_epoch = state.next_epoch();
|
let next_epoch = state.next_epoch()?;
|
||||||
|
|
||||||
// Reset eth1 data votes.
|
// Reset eth1 data votes.
|
||||||
if (state.slot + 1) % T::SlotsPerEth1VotingPeriod::to_u64() == 0 {
|
if state
|
||||||
|
.slot
|
||||||
|
.safe_add(1)?
|
||||||
|
.safe_rem(T::SlotsPerEth1VotingPeriod::to_u64())?
|
||||||
|
== 0
|
||||||
|
{
|
||||||
state.eth1_data_votes = VariableList::empty();
|
state.eth1_data_votes = VariableList::empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,10 @@ fn get_attestation_deltas<T: EthSpec>(
|
|||||||
validator_statuses: &ValidatorStatuses,
|
validator_statuses: &ValidatorStatuses,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<Vec<Delta>, Error> {
|
) -> Result<Vec<Delta>, Error> {
|
||||||
let finality_delay = (state.previous_epoch() - state.finalized_checkpoint.epoch).as_u64();
|
let finality_delay = state
|
||||||
|
.previous_epoch()
|
||||||
|
.safe_sub(state.finalized_checkpoint.epoch)?
|
||||||
|
.as_u64();
|
||||||
|
|
||||||
let mut deltas = vec![Delta::default(); state.validators.len()];
|
let mut deltas = vec![Delta::default(); state.validators.len()];
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ pub fn process_slashings<T: EthSpec>(
|
|||||||
|
|
||||||
for (index, validator) in state.validators.iter().enumerate() {
|
for (index, validator) in state.validators.iter().enumerate() {
|
||||||
if validator.slashed
|
if validator.slashed
|
||||||
&& epoch + T::EpochsPerSlashingsVector::to_u64().safe_div(2)?
|
&& epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
|
||||||
== validator.withdrawable_epoch
|
== validator.withdrawable_epoch
|
||||||
{
|
{
|
||||||
let increment = spec.effective_balance_increment;
|
let increment = spec.effective_balance_increment;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::super::common::initiate_validator_exit;
|
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
|
||||||
use super::Error;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
/// Performs a validator registry update, if required.
|
/// Performs a validator registry update, if required.
|
||||||
@ -31,7 +31,7 @@ pub fn process_registry_updates<T: EthSpec>(
|
|||||||
|
|
||||||
for index in indices_to_update {
|
for index in indices_to_update {
|
||||||
if state.validators[index].is_eligible_for_activation_queue(spec) {
|
if state.validators[index].is_eligible_for_activation_queue(spec) {
|
||||||
state.validators[index].activation_eligibility_epoch = current_epoch + 1;
|
state.validators[index].activation_eligibility_epoch = current_epoch.safe_add(1)?;
|
||||||
}
|
}
|
||||||
if is_ejectable(&state.validators[index]) {
|
if is_ejectable(&state.validators[index]) {
|
||||||
initiate_validator_exit(state, index, spec)?;
|
initiate_validator_exit(state, index, spec)?;
|
||||||
@ -50,7 +50,7 @@ pub fn process_registry_updates<T: EthSpec>(
|
|||||||
|
|
||||||
// Dequeue validators for activation up to churn limit
|
// Dequeue validators for activation up to churn limit
|
||||||
let churn_limit = state.get_churn_limit(spec)? as usize;
|
let churn_limit = state.get_churn_limit(spec)? as usize;
|
||||||
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec);
|
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
|
||||||
for index in activation_queue.into_iter().take(churn_limit) {
|
for index in activation_queue.into_iter().take(churn_limit) {
|
||||||
let validator = &mut state.validators[index];
|
let validator = &mut state.validators[index];
|
||||||
validator.activation_epoch = delayed_activation_epoch;
|
validator.activation_epoch = delayed_activation_epoch;
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
use crate::{per_epoch_processing::EpochProcessingSummary, *};
|
use crate::{per_epoch_processing::EpochProcessingSummary, *};
|
||||||
|
use safe_arith::{ArithError, SafeArith};
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
BeaconStateError(BeaconStateError),
|
BeaconStateError(BeaconStateError),
|
||||||
EpochProcessingError(EpochProcessingError),
|
EpochProcessingError(EpochProcessingError),
|
||||||
|
ArithError(ArithError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ArithError> for Error {
|
||||||
|
fn from(e: ArithError) -> Self {
|
||||||
|
Self::ArithError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advances a state forward by one slot, performing per-epoch processing if required.
|
/// Advances a state forward by one slot, performing per-epoch processing if required.
|
||||||
@ -21,14 +29,15 @@ pub fn per_slot_processing<T: EthSpec>(
|
|||||||
) -> Result<Option<EpochProcessingSummary>, Error> {
|
) -> Result<Option<EpochProcessingSummary>, Error> {
|
||||||
cache_state(state, state_root)?;
|
cache_state(state, state_root)?;
|
||||||
|
|
||||||
let summary = if state.slot > spec.genesis_slot && (state.slot + 1) % T::slots_per_epoch() == 0
|
let summary = if state.slot > spec.genesis_slot
|
||||||
|
&& state.slot.safe_add(1)?.safe_rem(T::slots_per_epoch())? == 0
|
||||||
{
|
{
|
||||||
Some(per_epoch_processing(state, spec)?)
|
Some(per_epoch_processing(state, spec)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
state.slot += 1;
|
state.slot.safe_add_assign(1)?;
|
||||||
|
|
||||||
Ok(summary)
|
Ok(summary)
|
||||||
}
|
}
|
||||||
@ -48,7 +57,7 @@ fn cache_state<T: EthSpec>(
|
|||||||
//
|
//
|
||||||
// This is a bit hacky, however it gets the job safely without lots of code.
|
// This is a bit hacky, however it gets the job safely without lots of code.
|
||||||
let previous_slot = state.slot;
|
let previous_slot = state.slot;
|
||||||
state.slot += 1;
|
state.slot.safe_add_assign(1)?;
|
||||||
|
|
||||||
// Store the previous slot's post state transition root.
|
// Store the previous slot's post state transition root.
|
||||||
state.set_state_root(previous_slot, previous_state_root)?;
|
state.set_state_root(previous_slot, previous_state_root)?;
|
||||||
@ -63,7 +72,7 @@ fn cache_state<T: EthSpec>(
|
|||||||
state.set_block_root(previous_slot, latest_block_root)?;
|
state.set_block_root(previous_slot, latest_block_root)?;
|
||||||
|
|
||||||
// Set the state slot back to what it should be.
|
// Set the state slot back to what it should be.
|
||||||
state.slot -= 1;
|
state.slot.safe_sub_assign(1)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,9 @@ serde_json = "1.0.52"
|
|||||||
criterion = "0.3.2"
|
criterion = "0.3.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sqlite"]
|
default = ["sqlite", "legacy-arith"]
|
||||||
|
# Allow saturating arithmetic on slots and epochs. Enabled by default, but deprecated.
|
||||||
|
legacy-arith = []
|
||||||
sqlite = ["rusqlite"]
|
sqlite = ["rusqlite"]
|
||||||
arbitrary-fuzz = [
|
arbitrary-fuzz = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
|
@ -100,10 +100,10 @@ enum AllowNextEpoch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AllowNextEpoch {
|
impl AllowNextEpoch {
|
||||||
fn upper_bound_of(self, current_epoch: Epoch) -> Epoch {
|
fn upper_bound_of(self, current_epoch: Epoch) -> Result<Epoch, Error> {
|
||||||
match self {
|
match self {
|
||||||
AllowNextEpoch::True => current_epoch + 1,
|
AllowNextEpoch::True => Ok(current_epoch.safe_add(1)?),
|
||||||
AllowNextEpoch::False => current_epoch,
|
AllowNextEpoch::False => Ok(current_epoch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,7 +323,9 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
pub fn previous_epoch(&self) -> Epoch {
|
pub fn previous_epoch(&self) -> Epoch {
|
||||||
let current_epoch = self.current_epoch();
|
let current_epoch = self.current_epoch();
|
||||||
if current_epoch > T::genesis_epoch() {
|
if current_epoch > T::genesis_epoch() {
|
||||||
current_epoch - 1
|
current_epoch
|
||||||
|
.safe_sub(1)
|
||||||
|
.expect("current epoch greater than genesis implies greater than 0")
|
||||||
} else {
|
} else {
|
||||||
current_epoch
|
current_epoch
|
||||||
}
|
}
|
||||||
@ -332,8 +334,8 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
/// The epoch following `self.current_epoch()`.
|
/// The epoch following `self.current_epoch()`.
|
||||||
///
|
///
|
||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
pub fn next_epoch(&self) -> Epoch {
|
pub fn next_epoch(&self) -> Result<Epoch, Error> {
|
||||||
self.current_epoch() + 1
|
Ok(self.current_epoch().safe_add(1)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the number of committees at `slot`.
|
/// Compute the number of committees at `slot`.
|
||||||
@ -378,7 +380,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<Vec<usize>, Error> {
|
) -> Result<Vec<usize>, Error> {
|
||||||
if epoch >= self.compute_activation_exit_epoch(self.current_epoch(), spec) {
|
if epoch >= self.compute_activation_exit_epoch(self.current_epoch(), spec)? {
|
||||||
Err(BeaconStateError::EpochOutOfBounds)
|
Err(BeaconStateError::EpochOutOfBounds)
|
||||||
} else {
|
} else {
|
||||||
Ok(get_active_validator_indices(&self.validators, epoch))
|
Ok(get_active_validator_indices(&self.validators, epoch))
|
||||||
@ -475,7 +477,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
{
|
{
|
||||||
return Ok(candidate_index);
|
return Ok(candidate_index);
|
||||||
}
|
}
|
||||||
i.increment()?;
|
i.safe_add_assign(1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +555,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
///
|
///
|
||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
fn get_latest_block_roots_index(&self, slot: Slot) -> Result<usize, Error> {
|
fn get_latest_block_roots_index(&self, slot: Slot) -> Result<usize, Error> {
|
||||||
if slot < self.slot && self.slot <= slot + self.block_roots.len() as u64 {
|
if slot < self.slot && self.slot <= slot.safe_add(self.block_roots.len() as u64)? {
|
||||||
Ok(slot.as_usize().safe_rem(self.block_roots.len())?)
|
Ok(slot.as_usize().safe_rem(self.block_roots.len())?)
|
||||||
} else {
|
} else {
|
||||||
Err(BeaconStateError::SlotOutOfBounds)
|
Err(BeaconStateError::SlotOutOfBounds)
|
||||||
@ -605,7 +607,9 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
let current_epoch = self.current_epoch();
|
let current_epoch = self.current_epoch();
|
||||||
let len = T::EpochsPerHistoricalVector::to_u64();
|
let len = T::EpochsPerHistoricalVector::to_u64();
|
||||||
|
|
||||||
if current_epoch < epoch + len && epoch <= allow_next_epoch.upper_bound_of(current_epoch) {
|
if current_epoch < epoch.safe_add(len)?
|
||||||
|
&& epoch <= allow_next_epoch.upper_bound_of(current_epoch)?
|
||||||
|
{
|
||||||
Ok(epoch.as_usize().safe_rem(len as usize)?)
|
Ok(epoch.as_usize().safe_rem(len as usize)?)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::EpochOutOfBounds)
|
Err(Error::EpochOutOfBounds)
|
||||||
@ -652,7 +656,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
///
|
///
|
||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
fn get_latest_state_roots_index(&self, slot: Slot) -> Result<usize, Error> {
|
fn get_latest_state_roots_index(&self, slot: Slot) -> Result<usize, Error> {
|
||||||
if slot < self.slot && self.slot <= slot + self.state_roots.len() as u64 {
|
if slot < self.slot && self.slot <= slot.safe_add(self.state_roots.len() as u64)? {
|
||||||
Ok(slot.as_usize().safe_rem(self.state_roots.len())?)
|
Ok(slot.as_usize().safe_rem(self.state_roots.len())?)
|
||||||
} else {
|
} else {
|
||||||
Err(BeaconStateError::SlotOutOfBounds)
|
Err(BeaconStateError::SlotOutOfBounds)
|
||||||
@ -672,7 +676,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
pub fn get_oldest_state_root(&self) -> Result<&Hash256, Error> {
|
pub fn get_oldest_state_root(&self) -> Result<&Hash256, Error> {
|
||||||
let i =
|
let i =
|
||||||
self.get_latest_state_roots_index(self.slot - Slot::from(self.state_roots.len()))?;
|
self.get_latest_state_roots_index(self.slot.saturating_sub(self.state_roots.len()))?;
|
||||||
Ok(&self.state_roots[i])
|
Ok(&self.state_roots[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,7 +684,9 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
///
|
///
|
||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
pub fn get_oldest_block_root(&self) -> Result<&Hash256, Error> {
|
pub fn get_oldest_block_root(&self) -> Result<&Hash256, Error> {
|
||||||
let i = self.get_latest_block_roots_index(self.slot - self.block_roots.len() as u64)?;
|
let i = self.get_latest_block_roots_index(
|
||||||
|
self.slot.saturating_sub(self.block_roots.len() as u64),
|
||||||
|
)?;
|
||||||
Ok(&self.block_roots[i])
|
Ok(&self.block_roots[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -712,8 +718,8 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
// We allow the slashings vector to be accessed at any cached epoch at or before
|
// We allow the slashings vector to be accessed at any cached epoch at or before
|
||||||
// the current epoch, or the next epoch if `AllowNextEpoch::True` is passed.
|
// the current epoch, or the next epoch if `AllowNextEpoch::True` is passed.
|
||||||
let current_epoch = self.current_epoch();
|
let current_epoch = self.current_epoch();
|
||||||
if current_epoch < epoch + T::EpochsPerSlashingsVector::to_u64()
|
if current_epoch < epoch.safe_add(T::EpochsPerSlashingsVector::to_u64())?
|
||||||
&& epoch <= allow_next_epoch.upper_bound_of(current_epoch)
|
&& epoch <= allow_next_epoch.upper_bound_of(current_epoch)?
|
||||||
{
|
{
|
||||||
Ok(epoch
|
Ok(epoch
|
||||||
.as_usize()
|
.as_usize()
|
||||||
@ -775,7 +781,10 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
// Bypass the safe getter for RANDAO so we can gracefully handle the scenario where `epoch
|
// Bypass the safe getter for RANDAO so we can gracefully handle the scenario where `epoch
|
||||||
// == 0`.
|
// == 0`.
|
||||||
let mix = {
|
let mix = {
|
||||||
let i = epoch + T::EpochsPerHistoricalVector::to_u64() - spec.min_seed_lookahead - 1;
|
let i = epoch
|
||||||
|
.safe_add(T::EpochsPerHistoricalVector::to_u64())?
|
||||||
|
.safe_sub(spec.min_seed_lookahead)?
|
||||||
|
.safe_sub(1)?;
|
||||||
self.randao_mixes[i.as_usize().safe_rem(self.randao_mixes.len())?]
|
self.randao_mixes[i.as_usize().safe_rem(self.randao_mixes.len())?]
|
||||||
};
|
};
|
||||||
let domain_bytes = int_to_bytes4(spec.get_domain_constant(domain_type));
|
let domain_bytes = int_to_bytes4(spec.get_domain_constant(domain_type));
|
||||||
@ -811,8 +820,12 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
|
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
|
||||||
///
|
///
|
||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
pub fn compute_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch {
|
pub fn compute_activation_exit_epoch(
|
||||||
epoch + 1 + spec.max_seed_lookahead
|
&self,
|
||||||
|
epoch: Epoch,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<Epoch, Error> {
|
||||||
|
Ok(epoch.safe_add(1)?.safe_add(spec.max_seed_lookahead)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the churn limit for the current epoch (number of validators who can leave per epoch).
|
/// Return the churn limit for the current epoch (number of validators who can leave per epoch).
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use super::BeaconState;
|
use super::BeaconState;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use core::num::NonZeroUsize;
|
use core::num::NonZeroUsize;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
@ -197,7 +198,7 @@ impl CommitteeCache {
|
|||||||
let epoch_start_slot = self.initialized_epoch?.start_slot(self.slots_per_epoch);
|
let epoch_start_slot = self.initialized_epoch?.start_slot(self.slots_per_epoch);
|
||||||
let slot_offset = global_committee_index / self.committees_per_slot;
|
let slot_offset = global_committee_index / self.committees_per_slot;
|
||||||
let index = global_committee_index % self.committees_per_slot;
|
let index = global_committee_index % self.committees_per_slot;
|
||||||
Some((epoch_start_slot + slot_offset, index))
|
Some((epoch_start_slot.safe_add(slot_offset).ok()?, index))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of active validators in the initialized epoch.
|
/// Returns the number of active validators in the initialized epoch.
|
||||||
|
@ -53,8 +53,8 @@ fn initializes_with_the_right_epoch() {
|
|||||||
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), &spec).unwrap();
|
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), &spec).unwrap();
|
||||||
assert_eq!(cache.initialized_epoch, Some(state.previous_epoch()));
|
assert_eq!(cache.initialized_epoch, Some(state.previous_epoch()));
|
||||||
|
|
||||||
let cache = CommitteeCache::initialized(&state, state.next_epoch(), &spec).unwrap();
|
let cache = CommitteeCache::initialized(&state, state.next_epoch().unwrap(), &spec).unwrap();
|
||||||
assert_eq!(cache.initialized_epoch, Some(state.next_epoch()));
|
assert_eq!(cache.initialized_epoch, Some(state.next_epoch().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -81,7 +81,7 @@ fn shuffles_for_the_right_epoch() {
|
|||||||
.get_seed(state.current_epoch(), Domain::BeaconAttester, spec)
|
.get_seed(state.current_epoch(), Domain::BeaconAttester, spec)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let next_seed = state
|
let next_seed = state
|
||||||
.get_seed(state.next_epoch(), Domain::BeaconAttester, spec)
|
.get_seed(state.next_epoch().unwrap(), Domain::BeaconAttester, spec)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!((previous_seed != current_seed) && (current_seed != next_seed));
|
assert!((previous_seed != current_seed) && (current_seed != next_seed));
|
||||||
@ -114,7 +114,7 @@ fn shuffles_for_the_right_epoch() {
|
|||||||
assert_eq!(cache.shuffling, shuffling_with_seed(previous_seed));
|
assert_eq!(cache.shuffling, shuffling_with_seed(previous_seed));
|
||||||
assert_shuffling_positions_accurate(&cache);
|
assert_shuffling_positions_accurate(&cache);
|
||||||
|
|
||||||
let cache = CommitteeCache::initialized(&state, state.next_epoch(), spec).unwrap();
|
let cache = CommitteeCache::initialized(&state, state.next_epoch().unwrap(), spec).unwrap();
|
||||||
assert_eq!(cache.shuffling, shuffling_with_seed(next_seed));
|
assert_eq!(cache.shuffling, shuffling_with_seed(next_seed));
|
||||||
assert_shuffling_positions_accurate(&cache);
|
assert_shuffling_positions_accurate(&cache);
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ impl ExitCache {
|
|||||||
self.exit_epoch_counts
|
self.exit_epoch_counts
|
||||||
.entry(exit_epoch)
|
.entry(exit_epoch)
|
||||||
.or_insert(0)
|
.or_insert(0)
|
||||||
.increment()?;
|
.safe_add_assign(1)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
|
use safe_arith::{ArithError, SafeArith};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
EpochTooLow { base: Epoch, other: Epoch },
|
EpochTooLow { base: Epoch, other: Epoch },
|
||||||
EpochTooHigh { base: Epoch, other: Epoch },
|
EpochTooHigh { base: Epoch, other: Epoch },
|
||||||
|
ArithError(ArithError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ArithError> for Error {
|
||||||
|
fn from(e: ArithError) -> Self {
|
||||||
|
Self::ArithError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "arbitrary-fuzz")]
|
#[cfg(feature = "arbitrary-fuzz")]
|
||||||
@ -32,8 +40,8 @@ impl RelativeEpoch {
|
|||||||
match self {
|
match self {
|
||||||
// Due to saturating nature of epoch, check for current first.
|
// Due to saturating nature of epoch, check for current first.
|
||||||
RelativeEpoch::Current => base,
|
RelativeEpoch::Current => base,
|
||||||
RelativeEpoch::Previous => base - 1,
|
RelativeEpoch::Previous => base.saturating_sub(1u64),
|
||||||
RelativeEpoch::Next => base + 1,
|
RelativeEpoch::Next => base.saturating_add(1u64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,12 +54,11 @@ impl RelativeEpoch {
|
|||||||
///
|
///
|
||||||
/// Spec v0.12.1
|
/// Spec v0.12.1
|
||||||
pub fn from_epoch(base: Epoch, other: Epoch) -> Result<Self, Error> {
|
pub fn from_epoch(base: Epoch, other: Epoch) -> Result<Self, Error> {
|
||||||
// Due to saturating nature of epoch, check for current first.
|
|
||||||
if other == base {
|
if other == base {
|
||||||
Ok(RelativeEpoch::Current)
|
Ok(RelativeEpoch::Current)
|
||||||
} else if other == base - 1 {
|
} else if other.safe_add(1)? == base {
|
||||||
Ok(RelativeEpoch::Previous)
|
Ok(RelativeEpoch::Previous)
|
||||||
} else if other == base + 1 {
|
} else if other == base.safe_add(1)? {
|
||||||
Ok(RelativeEpoch::Next)
|
Ok(RelativeEpoch::Next)
|
||||||
} else if other < base {
|
} else if other < base {
|
||||||
Err(Error::EpochTooLow { base, other })
|
Err(Error::EpochTooLow { base, other })
|
||||||
|
@ -14,21 +14,23 @@ use crate::test_utils::TestRandom;
|
|||||||
use crate::SignedRoot;
|
use crate::SignedRoot;
|
||||||
|
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz::{ssz_encode, Decode, DecodeError, Encode};
|
use ssz::{ssz_encode, Decode, DecodeError, Encode};
|
||||||
use std::cmp::{Ord, Ordering};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hash;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy-arith")]
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
|
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
|
||||||
|
|
||||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||||
#[derive(Eq, Clone, Copy, Default, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Slot(u64);
|
pub struct Slot(u64);
|
||||||
|
|
||||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||||
#[derive(Eq, Clone, Copy, Default, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Epoch(u64);
|
pub struct Epoch(u64);
|
||||||
|
|
||||||
@ -41,7 +43,9 @@ impl Slot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn epoch(self, slots_per_epoch: u64) -> Epoch {
|
pub fn epoch(self, slots_per_epoch: u64) -> Epoch {
|
||||||
Epoch::from(self.0) / Epoch::from(slots_per_epoch)
|
Epoch::new(self.0)
|
||||||
|
.safe_div(slots_per_epoch)
|
||||||
|
.expect("slots_per_epoch is not 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_value() -> Slot {
|
pub fn max_value() -> Slot {
|
||||||
@ -96,9 +100,6 @@ impl Epoch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignedRoot for Epoch {}
|
|
||||||
impl SignedRoot for Slot {}
|
|
||||||
|
|
||||||
pub struct SlotIter<'a> {
|
pub struct SlotIter<'a> {
|
||||||
current_iteration: u64,
|
current_iteration: u64,
|
||||||
epoch: &'a Epoch,
|
epoch: &'a Epoch,
|
||||||
@ -115,7 +116,7 @@ impl<'a> Iterator for SlotIter<'a> {
|
|||||||
let start_slot = self.epoch.start_slot(self.slots_per_epoch);
|
let start_slot = self.epoch.start_slot(self.slots_per_epoch);
|
||||||
let previous = self.current_iteration;
|
let previous = self.current_iteration;
|
||||||
self.current_iteration = self.current_iteration.checked_add(1)?;
|
self.current_iteration = self.current_iteration.checked_add(1)?;
|
||||||
Some(start_slot + previous)
|
start_slot.safe_add(previous).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,22 +42,84 @@ macro_rules! impl_from_into_usize {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_u64_eq_ord {
|
||||||
|
($type: ident) => {
|
||||||
|
impl PartialEq<u64> for $type {
|
||||||
|
fn eq(&self, other: &u64) -> bool {
|
||||||
|
self.as_u64() == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd<u64> for $type {
|
||||||
|
fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
|
||||||
|
self.as_u64().partial_cmp(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_safe_arith {
|
||||||
|
($type: ident, $rhs_ty: ident) => {
|
||||||
|
impl safe_arith::SafeArith<$rhs_ty> for $type {
|
||||||
|
const ZERO: Self = $type::new(0);
|
||||||
|
const ONE: Self = $type::new(1);
|
||||||
|
|
||||||
|
fn safe_add(&self, other: $rhs_ty) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_add(other.into())
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::Overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_sub(&self, other: $rhs_ty) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_sub(other.into())
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::Overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_mul(&self, other: $rhs_ty) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_mul(other.into())
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::Overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_div(&self, other: $rhs_ty) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_div(other.into())
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::DivisionByZero)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_rem(&self, other: $rhs_ty) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_rem(other.into())
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::DivisionByZero)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_shl(&self, other: u32) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_shl(other)
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::Overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_shr(&self, other: u32) -> safe_arith::Result<Self> {
|
||||||
|
self.0
|
||||||
|
.checked_shr(other)
|
||||||
|
.map(Self::new)
|
||||||
|
.ok_or(safe_arith::ArithError::Overflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: prefer `SafeArith` methods for new code.
|
||||||
|
#[cfg(feature = "legacy-arith")]
|
||||||
macro_rules! impl_math_between {
|
macro_rules! impl_math_between {
|
||||||
($main: ident, $other: ident) => {
|
($main: ident, $other: ident) => {
|
||||||
impl PartialOrd<$other> for $main {
|
|
||||||
/// Utilizes `partial_cmp` on the underlying `u64`.
|
|
||||||
fn partial_cmp(&self, other: &$other) -> Option<Ordering> {
|
|
||||||
Some(self.0.cmp(&(*other).into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<$other> for $main {
|
|
||||||
fn eq(&self, other: &$other) -> bool {
|
|
||||||
let other: u64 = (*other).into();
|
|
||||||
self.0 == other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<$other> for $main {
|
impl Add<$other> for $main {
|
||||||
type Output = $main;
|
type Output = $main;
|
||||||
|
|
||||||
@ -144,33 +206,17 @@ macro_rules! impl_math {
|
|||||||
($type: ident) => {
|
($type: ident) => {
|
||||||
impl $type {
|
impl $type {
|
||||||
pub fn saturating_sub<T: Into<$type>>(&self, other: T) -> $type {
|
pub fn saturating_sub<T: Into<$type>>(&self, other: T) -> $type {
|
||||||
*self - other.into()
|
$type::new(self.as_u64().saturating_sub(other.into().as_u64()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn saturating_add<T: Into<$type>>(&self, other: T) -> $type {
|
pub fn saturating_add<T: Into<$type>>(&self, other: T) -> $type {
|
||||||
*self + other.into()
|
$type::new(self.as_u64().saturating_add(other.into().as_u64()))
|
||||||
}
|
|
||||||
|
|
||||||
pub fn checked_div<T: Into<$type>>(&self, rhs: T) -> Option<$type> {
|
|
||||||
let rhs: $type = rhs.into();
|
|
||||||
if rhs == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(*self / rhs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_power_of_two(&self) -> bool {
|
pub fn is_power_of_two(&self) -> bool {
|
||||||
self.0.is_power_of_two()
|
self.0.is_power_of_two()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for $type {
|
|
||||||
fn cmp(&self, other: &$type) -> Ordering {
|
|
||||||
let other: u64 = (*other).into();
|
|
||||||
self.0.cmp(&other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,6 +303,8 @@ macro_rules! impl_ssz {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SignedRoot for $type {}
|
||||||
|
|
||||||
impl TestRandom for $type {
|
impl TestRandom for $type {
|
||||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||||
$type::from(u64::random_for_test(rng))
|
$type::from(u64::random_for_test(rng))
|
||||||
@ -265,29 +313,21 @@ macro_rules! impl_ssz {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_hash {
|
|
||||||
($type: ident) => {
|
|
||||||
// Implemented to stop clippy lint:
|
|
||||||
// https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
|
|
||||||
impl Hash for $type {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
ssz_encode(self).hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_common {
|
macro_rules! impl_common {
|
||||||
($type: ident) => {
|
($type: ident) => {
|
||||||
impl_from_into_u64!($type);
|
impl_from_into_u64!($type);
|
||||||
impl_from_into_usize!($type);
|
impl_from_into_usize!($type);
|
||||||
|
impl_u64_eq_ord!($type);
|
||||||
|
impl_safe_arith!($type, $type);
|
||||||
|
impl_safe_arith!($type, u64);
|
||||||
|
#[cfg(feature = "legacy-arith")]
|
||||||
impl_math_between!($type, $type);
|
impl_math_between!($type, $type);
|
||||||
|
#[cfg(feature = "legacy-arith")]
|
||||||
impl_math_between!($type, u64);
|
impl_math_between!($type, u64);
|
||||||
impl_math!($type);
|
impl_math!($type);
|
||||||
impl_display!($type);
|
impl_display!($type);
|
||||||
impl_debug!($type);
|
impl_debug!($type);
|
||||||
impl_ssz!($type);
|
impl_ssz!($type);
|
||||||
impl_hash!($type);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,6 +375,7 @@ macro_rules! math_between_tests {
|
|||||||
($type: ident, $other: ident) => {
|
($type: ident, $other: ident) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn partial_ord() {
|
fn partial_ord() {
|
||||||
|
use std::cmp::Ordering;
|
||||||
let assert_partial_ord = |a: u64, partial_ord: Ordering, b: u64| {
|
let assert_partial_ord = |a: u64, partial_ord: Ordering, b: u64| {
|
||||||
let other: $other = $type(b).into();
|
let other: $other = $type(b).into();
|
||||||
assert_eq!($type(a).partial_cmp(&other), Some(partial_ord));
|
assert_eq!($type(a).partial_cmp(&other), Some(partial_ord));
|
||||||
@ -518,7 +559,7 @@ macro_rules! math_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn checked_div() {
|
fn checked_div() {
|
||||||
let assert_checked_div = |a: u64, b: u64, result: Option<u64>| {
|
let assert_checked_div = |a: u64, b: u64, result: Option<u64>| {
|
||||||
let division_result_as_u64 = match $type(a).checked_div($type(b)) {
|
let division_result_as_u64 = match $type(a).safe_div($type(b)).ok() {
|
||||||
None => None,
|
None => None,
|
||||||
Some(val) => Some(val.as_u64()),
|
Some(val) => Some(val.as_u64()),
|
||||||
};
|
};
|
||||||
@ -560,6 +601,7 @@ macro_rules! math_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ord() {
|
fn ord() {
|
||||||
|
use std::cmp::Ordering;
|
||||||
let assert_ord = |a: u64, ord: Ordering, b: u64| {
|
let assert_ord = |a: u64, ord: Ordering, b: u64| {
|
||||||
assert_eq!($type(a).cmp(&$type(b)), ord);
|
assert_eq!($type(a).cmp(&$type(b)), ord);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::test_utils::AttestationTestTask;
|
use crate::test_utils::AttestationTestTask;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
|
|
||||||
/// Builds an `AttestationData` to be used for testing purposes.
|
/// Builds an `AttestationData` to be used for testing purposes.
|
||||||
///
|
///
|
||||||
@ -49,12 +50,19 @@ impl TestingAttestationDataBuilder {
|
|||||||
|
|
||||||
match test_task {
|
match test_task {
|
||||||
AttestationTestTask::IncludedTooEarly => {
|
AttestationTestTask::IncludedTooEarly => {
|
||||||
slot = state.slot - spec.min_attestation_inclusion_delay + 1
|
slot = state
|
||||||
|
.slot
|
||||||
|
.safe_sub(spec.min_attestation_inclusion_delay)
|
||||||
|
.unwrap()
|
||||||
|
.safe_add(1u64)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
AttestationTestTask::IncludedTooLate => slot -= T::SlotsPerEpoch::to_u64(),
|
AttestationTestTask::IncludedTooLate => slot
|
||||||
|
.safe_sub_assign(Slot::new(T::SlotsPerEpoch::to_u64()))
|
||||||
|
.unwrap(),
|
||||||
AttestationTestTask::TargetEpochSlotMismatch => {
|
AttestationTestTask::TargetEpochSlotMismatch => {
|
||||||
target = Checkpoint {
|
target = Checkpoint {
|
||||||
epoch: current_epoch + 1,
|
epoch: current_epoch.safe_add(1u64).unwrap(),
|
||||||
root: Hash256::zero(),
|
root: Hash256::zero(),
|
||||||
};
|
};
|
||||||
assert_ne!(target.epoch, slot.epoch(T::slots_per_epoch()));
|
assert_ne!(target.epoch, slot.epoch(T::slots_per_epoch()));
|
||||||
|
@ -9,6 +9,7 @@ use crate::{
|
|||||||
use int_to_bytes::int_to_bytes32;
|
use int_to_bytes::int_to_bytes32;
|
||||||
use merkle_proof::MerkleTree;
|
use merkle_proof::MerkleTree;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
|
|
||||||
/// Builds a beacon block to be used for testing purposes.
|
/// Builds a beacon block to be used for testing purposes.
|
||||||
@ -172,7 +173,10 @@ impl<T: EthSpec> TestingBeaconBlockBuilder<T> {
|
|||||||
num_attestations: usize,
|
num_attestations: usize,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), BeaconStateError> {
|
) -> Result<(), BeaconStateError> {
|
||||||
let mut slot = self.block.slot - spec.min_attestation_inclusion_delay;
|
let mut slot = self
|
||||||
|
.block
|
||||||
|
.slot
|
||||||
|
.safe_sub(spec.min_attestation_inclusion_delay)?;
|
||||||
let mut attestations_added = 0;
|
let mut attestations_added = 0;
|
||||||
|
|
||||||
// Stores the following (in order):
|
// Stores the following (in order):
|
||||||
@ -192,7 +196,7 @@ impl<T: EthSpec> TestingBeaconBlockBuilder<T> {
|
|||||||
// - The slot is too old to be included in a block at this slot.
|
// - The slot is too old to be included in a block at this slot.
|
||||||
// - The `MAX_ATTESTATIONS`.
|
// - The `MAX_ATTESTATIONS`.
|
||||||
loop {
|
loop {
|
||||||
if state.slot >= slot + T::slots_per_epoch() {
|
if state.slot >= slot.safe_add(T::slots_per_epoch())? {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +215,7 @@ impl<T: EthSpec> TestingBeaconBlockBuilder<T> {
|
|||||||
attestations_added += 1;
|
attestations_added += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
slot -= 1;
|
slot.safe_sub_assign(1u64)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through all the committees, splitting each one in half until we have
|
// Loop through all the committees, splitting each one in half until we have
|
||||||
|
@ -143,11 +143,11 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
|||||||
|
|
||||||
state.slot = slot;
|
state.slot = slot;
|
||||||
|
|
||||||
state.previous_justified_checkpoint.epoch = epoch - 3;
|
state.previous_justified_checkpoint.epoch = epoch.saturating_sub(3u64);
|
||||||
state.current_justified_checkpoint.epoch = epoch - 2;
|
state.current_justified_checkpoint.epoch = epoch.saturating_sub(2u64);
|
||||||
state.justification_bits = BitVector::from_bytes(vec![0b0000_1111]).unwrap();
|
state.justification_bits = BitVector::from_bytes(vec![0b0000_1111]).unwrap();
|
||||||
|
|
||||||
state.finalized_checkpoint.epoch = epoch - 3;
|
state.finalized_checkpoint.epoch = state.previous_justified_checkpoint.epoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
|
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
|
||||||
|
Loading…
Reference in New Issue
Block a user