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

Use checked arithmetic in types and state proc #1009

Merged
merged 2 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,10 @@ jobs:
- uses: actions/checkout@v1
- name: Typecheck benchmark code without running it
run: make check-benches
clippy:
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Lint code for quality and style with Clippy
run: make lint
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"eth2/utils/lighthouse_bootstrap",
"eth2/utils/merkle_proof",
"eth2/utils/int_to_bytes",
"eth2/utils/safe_arith",
"eth2/utils/serde_hex",
"eth2/utils/slot_clock",
"eth2/utils/ssz",
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ test: test-release
# Runs the entire test suite, downloading test vectors if required.
test-full: cargo-fmt test-release test-debug test-ef

# Lints the code for bad style and potentially unsafe arithmetic using Clippy.
# Clippy lints are opt-in per-crate for now, which is why we allow all by default.
lint:
cargo clippy --all -- -A clippy::all

# Runs the makefile in the `ef_tests` repo.
#
# May download and extract an archive of test vectors from the ethereum
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ rand = "0.7.2"
proto_array_fork_choice = { path = "../../eth2/proto_array_fork_choice" }
lru = "0.4.3"
tempfile = "3.1.0"
safe_arith = { path = "../../eth2/utils/safe_arith" }

[dev-dependencies]
lazy_static = "1.4.0"
Expand Down
10 changes: 9 additions & 1 deletion beacon_node/beacon_chain/src/eth1_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ pub enum Error {
///
/// The eth1 caches are stale, or a junk value was voted into the chain.
UnknownPreviousEth1BlockHash,
/// An arithmetic error occurred.
ArithError(safe_arith::ArithError),
}

impl From<safe_arith::ArithError> for Error {
fn from(e: safe_arith::ArithError) -> Self {
Self::ArithError(e)
}
}

#[derive(Encode, Decode, Clone)]
Expand Down Expand Up @@ -369,7 +377,7 @@ impl<T: EthSpec, S: Store<T>> Eth1ChainBackend<T, S> for CachingEth1Backend<T, S
_spec: &ChainSpec,
) -> Result<Vec<Deposit>, Error> {
let deposit_index = state.eth1_deposit_index;
let deposit_count = if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data_vote) {
let deposit_count = if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data_vote)? {
new_eth1_data.deposit_count
} else {
state.eth1_data.deposit_count
Expand Down
3 changes: 2 additions & 1 deletion beacon_node/genesis/src/eth1_genesis_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,8 @@ impl Eth1GenesisService {
.map_err(|e| format!("Error whilst processing deposit: {:?}", e))
})?;

process_activations(&mut local_state, spec);
process_activations(&mut local_state, spec)
.map_err(|e| format!("Error whilst processing activations: {:?}", e))?;
let is_valid = is_valid_genesis_state(&local_state, spec);

trace!(
Expand Down
1 change: 1 addition & 0 deletions eth2/state_processing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ eth2_ssz = "0.1.2"
eth2_ssz_types = { path = "../utils/ssz_types" }
merkle_proof = { path = "../utils/merkle_proof" }
log = "0.4.8"
safe_arith = { path = "../utils/safe_arith" }
tree_hash = "0.1.0"
tree_hash_derive = "0.2"
types = { path = "../types" }
Expand Down
3 changes: 2 additions & 1 deletion eth2/state_processing/src/common/deposit_data_tree.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use eth2_hashing::hash;
use int_to_bytes::int_to_bytes32;
use merkle_proof::{MerkleTree, MerkleTreeError};
use safe_arith::SafeArith;
use types::Hash256;

/// Emulates the eth1 deposit contract merkle tree.
Expand Down Expand Up @@ -46,7 +47,7 @@ impl DepositDataTree {
/// Add a deposit to the merkle tree.
pub fn push_leaf(&mut self, leaf: Hash256) -> Result<(), MerkleTreeError> {
self.tree.push_leaf(leaf, self.depth)?;
self.mix_in_length += 1;
self.mix_in_length.increment()?;
Ok(())
}
}
11 changes: 6 additions & 5 deletions eth2/state_processing/src/common/get_base_reward.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use integer_sqrt::IntegerSquareRoot;
use safe_arith::SafeArith;
use types::*;

/// Returns the base reward for some validator.
Expand All @@ -14,10 +15,10 @@ pub fn get_base_reward<T: EthSpec>(
if total_active_balance == 0 {
Ok(0)
} else {
Ok(
state.get_effective_balance(index, spec)? * spec.base_reward_factor
/ total_active_balance.integer_sqrt()
/ spec.base_rewards_per_epoch,
)
Ok(state
.get_effective_balance(index, spec)?
.safe_mul(spec.base_reward_factor)?
.safe_div(total_active_balance.integer_sqrt())?
.safe_div(spec.base_rewards_per_epoch)?)
}
}
12 changes: 8 additions & 4 deletions eth2/state_processing/src/common/slash_validator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::common::initiate_validator_exit;
use safe_arith::SafeArith;
use std::cmp;
use types::{BeaconStateError as Error, *};

Expand Down Expand Up @@ -27,18 +28,21 @@ pub fn slash_validator<T: EthSpec>(
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
state.set_slashings(
epoch,
state.get_slashings(epoch)? + validator_effective_balance,
state
.get_slashings(epoch)?
.safe_add(validator_effective_balance)?,
)?;
safe_sub_assign!(
state.balances[slashed_index],
validator_effective_balance / spec.min_slashing_penalty_quotient
validator_effective_balance.safe_div(spec.min_slashing_penalty_quotient)?
);

// Apply proposer and whistleblower rewards
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?;
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
let whistleblower_reward = validator_effective_balance / spec.whistleblower_reward_quotient;
let proposer_reward = whistleblower_reward / spec.proposer_reward_quotient;
let whistleblower_reward =
validator_effective_balance.safe_div(spec.whistleblower_reward_quotient)?;
let proposer_reward = whistleblower_reward.safe_div(spec.proposer_reward_quotient)?;

safe_add_assign!(state.balances[proposer_index], proposer_reward);
safe_add_assign!(
Expand Down
16 changes: 11 additions & 5 deletions eth2/state_processing/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::per_block_processing::{errors::BlockProcessingError, process_deposit};
use crate::common::DepositDataTree;
use safe_arith::SafeArith;
use tree_hash::TreeHash;
use types::DEPOSIT_TREE_DEPTH;
use types::*;
Expand All @@ -14,8 +15,9 @@ pub fn initialize_beacon_state_from_eth1<T: EthSpec>(
deposits: Vec<Deposit>,
spec: &ChainSpec,
) -> Result<BeaconState<T>, BlockProcessingError> {
let genesis_time =
eth1_timestamp - eth1_timestamp % spec.min_genesis_delay + 2 * spec.min_genesis_delay;
let genesis_time = eth1_timestamp
.safe_sub(eth1_timestamp.safe_rem(spec.min_genesis_delay)?)?
.safe_add(2.safe_mul(spec.min_genesis_delay)?)?;
let eth1_data = Eth1Data {
// Temporary deposit root
deposit_root: Hash256::zero(),
Expand All @@ -37,7 +39,7 @@ pub fn initialize_beacon_state_from_eth1<T: EthSpec>(
process_deposit(&mut state, &deposit, spec, true)?;
}

process_activations(&mut state, spec);
process_activations(&mut state, spec)?;

// Now that we have our validators, initialize the caches (including the committees)
state.build_all_caches(spec)?;
Expand All @@ -60,16 +62,20 @@ pub fn is_valid_genesis_state<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSp
/// Activate genesis validators, if their balance is acceptable.
///
/// Spec v0.11.1
pub fn process_activations<T: EthSpec>(state: &mut BeaconState<T>, spec: &ChainSpec) {
pub fn process_activations<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
for (index, validator) in state.validators.iter_mut().enumerate() {
let balance = state.balances[index];
validator.effective_balance = std::cmp::min(
balance - balance % spec.effective_balance_increment,
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
);
if validator.effective_balance == spec.max_effective_balance {
validator.activation_eligibility_epoch = T::genesis_epoch();
validator.activation_epoch = T::genesis_epoch();
}
}
Ok(())
}
2 changes: 2 additions & 0 deletions eth2/state_processing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![deny(clippy::integer_arithmetic)]

#[macro_use]
mod macros;

Expand Down
29 changes: 18 additions & 11 deletions eth2/state_processing/src/per_block_processing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::common::{initiate_validator_exit, slash_validator};
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid, IntoWithIndex};
use rayon::prelude::*;
use safe_arith::{ArithError, SafeArith};
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
use std::convert::TryInto;
use tree_hash::TreeHash;
Expand Down Expand Up @@ -239,7 +240,7 @@ pub fn process_eth1_data<T: EthSpec>(
state: &mut BeaconState<T>,
eth1_data: &Eth1Data,
) -> Result<(), Error> {
if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data) {
if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data)? {
state.eth1_data = new_eth1_data;
}

Expand All @@ -248,25 +249,25 @@ pub fn process_eth1_data<T: EthSpec>(
Ok(())
}

/// Returns `Some(eth1_data)` if adding the given `eth1_data` to `state.eth1_data_votes` would
/// Returns `Ok(Some(eth1_data))` if adding the given `eth1_data` to `state.eth1_data_votes` would
/// result in a change to `state.eth1_data`.
///
/// Spec v0.11.1
pub fn get_new_eth1_data<T: EthSpec>(
state: &BeaconState<T>,
eth1_data: &Eth1Data,
) -> Option<Eth1Data> {
) -> Result<Option<Eth1Data>, ArithError> {
let num_votes = state
.eth1_data_votes
.iter()
.filter(|vote| *vote == eth1_data)
.count();

// The +1 is to account for the `eth1_data` supplied to the function.
if 2 * (num_votes + 1) > T::SlotsPerEth1VotingPeriod::to_usize() {
Some(eth1_data.clone())
if num_votes.safe_add(1)?.safe_mul(2)? > T::SlotsPerEth1VotingPeriod::to_usize() {
Ok(Some(eth1_data.clone()))
} else {
None
Ok(None)
}
}

Expand Down Expand Up @@ -318,7 +319,8 @@ pub fn process_attester_slashings<T: EthSpec>(
) -> Result<(), BlockProcessingError> {
// Verify the `IndexedAttestation`s in parallel (these are the resource-consuming objects, not
// the `AttesterSlashing`s themselves).
let mut indexed_attestations: Vec<&_> = Vec::with_capacity(attester_slashings.len() * 2);
let mut indexed_attestations: Vec<&_> =
Vec::with_capacity(attester_slashings.len().safe_mul(2)?);
for attester_slashing in attester_slashings {
indexed_attestations.push(&attester_slashing.attestation_1);
indexed_attestations.push(&attester_slashing.attestation_2);
Expand Down Expand Up @@ -432,8 +434,13 @@ pub fn process_deposits<T: EthSpec>(
.par_iter()
.enumerate()
.try_for_each(|(i, deposit)| {
verify_deposit_merkle_proof(state, deposit, state.eth1_deposit_index + i as u64, spec)
.map_err(|e| e.into_with_index(i))
verify_deposit_merkle_proof(
state,
deposit,
state.eth1_deposit_index.safe_add(i as u64)?,
spec,
)
.map_err(|e| e.into_with_index(i))
})?;

// Update the state in series.
Expand All @@ -459,7 +466,7 @@ pub fn process_deposit<T: EthSpec>(
.map_err(|e| e.into_with_index(deposit_index))?;
}

state.eth1_deposit_index += 1;
state.eth1_deposit_index.increment()?;

// Ensure the state's pubkey cache is fully up-to-date, it will be used to check to see if the
// depositing validator already exists in the registry.
Expand Down Expand Up @@ -495,7 +502,7 @@ pub fn process_deposit<T: EthSpec>(
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
effective_balance: std::cmp::min(
amount - amount % spec.effective_balance_increment,
amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
),
slashed: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::integer_arithmetic)]
paulhauner marked this conversation as resolved.
Show resolved Hide resolved

use super::signature_sets::{Error as SignatureSetError, Result as SignatureSetResult, *};

use crate::common::get_indexed_attestation;
Expand Down
Loading