diff --git a/eth2/state_processing/Cargo.toml b/eth2/state_processing/Cargo.toml index 4b031022a7..0fc7910c89 100644 --- a/eth2/state_processing/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -29,3 +29,6 @@ tree_hash = { path = "../utils/tree_hash" } tree_hash_derive = { path = "../utils/tree_hash_derive" } types = { path = "../types" } rayon = "1.0" + +[features] +fake_crypto = ["bls/fake_crypto"] \ No newline at end of file diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 5bb40e25b9..251d7cd911 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -20,7 +20,9 @@ pub use verify_transfer::{ execute_transfer, verify_transfer, verify_transfer_time_independent_only, }; +pub mod block_processing_builder; pub mod errors; +pub mod tests; mod validate_attestation; mod verify_attester_slashing; mod verify_deposit; diff --git a/eth2/state_processing/src/per_block_processing/block_processing_builder.rs b/eth2/state_processing/src/per_block_processing/block_processing_builder.rs new file mode 100644 index 0000000000..307fc4a3df --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/block_processing_builder.rs @@ -0,0 +1,60 @@ +use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; +use types::*; +use tree_hash::SignedRoot; + +pub struct BlockProcessingBuilder { + pub state_builder: TestingBeaconStateBuilder, + pub block_builder: TestingBeaconBlockBuilder, + + pub num_validators: usize, +} + +impl BlockProcessingBuilder { + pub fn new(num_validators: usize, spec: &ChainSpec) -> Self { + let state_builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec); + let block_builder = TestingBeaconBlockBuilder::new(spec); + + Self { + state_builder, + block_builder, + num_validators: 0, + } + } + + pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) { + self.state_builder.teleport_to_slot(slot, &spec); + } + + pub fn build_caches(&mut self, spec: &ChainSpec) { + // Builds all caches; benches will not contain shuffling/committee building times. + self.state_builder.build_caches(&spec).unwrap(); + } + + pub fn build(mut self, randao_sk: Option, previous_block_root: Option, spec: &ChainSpec) -> (BeaconBlock, BeaconState) { + let (state, keypairs) = self.state_builder.build(); + let builder = &mut self.block_builder; + + builder.set_slot(state.slot); + + match previous_block_root { + Some(root) => builder.set_previous_block_root(root), + None => builder.set_previous_block_root(Hash256::from_slice(&state.latest_block_header.signed_root())), + } + + let proposer_index = state + .get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec) + .unwrap(); + let keypair = &keypairs[proposer_index]; + + match randao_sk { + Some(sk) => builder.set_randao_reveal(&sk, &state.fork, spec), + None => builder.set_randao_reveal(&keypair.sk, &state.fork, spec), + } + + let block = self.block_builder.build(&keypair.sk, &state.fork, spec); + + (block, state) + } + +} diff --git a/eth2/state_processing/src/per_block_processing/tests.rs b/eth2/state_processing/src/per_block_processing/tests.rs new file mode 100644 index 0000000000..715c3e852d --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/tests.rs @@ -0,0 +1,110 @@ +#![cfg(all(test, not(feature = "fake_crypto")))] +use super::block_processing_builder::BlockProcessingBuilder; +use super::errors::*; +use crate::per_block_processing; +use tree_hash::SignedRoot; +use types::*; + +pub const VALIDATOR_COUNT: usize = 10; + +#[test] +fn valid_block_ok() { + let spec = FoundationEthSpec::spec(); + let builder = get_builder(&spec); + let (block, mut state) = builder.build(None, None, &spec); + + let result = per_block_processing(&mut state, &block, &spec); + + assert_eq!(result, Ok(())); +} + +#[test] +fn invalid_block_header_state_slot() { + let spec = FoundationEthSpec::spec(); + let builder = get_builder(&spec); + let (mut block, mut state) = builder.build(None, None, &spec); + + state.slot = Slot::new(133713); + block.slot = Slot::new(424242); + + let result = per_block_processing(&mut state, &block, &spec); + + assert_eq!( + result, + Err(BlockProcessingError::Invalid( + BlockInvalid::StateSlotMismatch + )) + ); +} + +#[test] +fn invalid_parent_block_root() { + let spec = FoundationEthSpec::spec(); + let builder = get_builder(&spec); + let invalid_parent_root = Hash256::from([0xAA; 32]); + let (block, mut state) = builder.build(None, Some(invalid_parent_root), &spec); + + let result = per_block_processing(&mut state, &block, &spec); + + assert_eq!( + result, + Err(BlockProcessingError::Invalid( + BlockInvalid::ParentBlockRootMismatch{ + state: Hash256::from_slice(&state.latest_block_header.signed_root()), + block: block.previous_block_root + } + )) + ); +} + +#[test] +fn invalid_block_signature() { + let spec = FoundationEthSpec::spec(); + let builder = get_builder(&spec); + let (mut block, mut state) = builder.build(None, None, &spec); + + // sign the block with a keypair that is not the expected proposer + let keypair = Keypair::random(); + let message = block.signed_root(); + let epoch = block.slot.epoch(spec.slots_per_epoch); + let domain = spec.get_domain(epoch, Domain::BeaconBlock, &state.fork); + block.signature = Signature::new(&message, domain, &keypair.sk); + + // process block with invalid block signature + let result = per_block_processing(&mut state, &block, &spec); + + // should get a BadSignature error + assert_eq!( + result, + Err(BlockProcessingError::Invalid(BlockInvalid::BadSignature)) + ); +} + +#[test] +fn invalid_randao_reveal_signature() { + let spec = FoundationEthSpec::spec(); + let builder = get_builder(&spec); + + // sign randao reveal with random keypair + let keypair = Keypair::random(); + let (block, mut state) = builder.build(Some(keypair.sk), None, &spec); + + let result = per_block_processing(&mut state, &block, &spec); + + // should get a BadRandaoSignature error + assert_eq!( + result, + Err(BlockProcessingError::Invalid(BlockInvalid::BadRandaoSignature)) + ); +} + +fn get_builder(spec: &ChainSpec) -> (BlockProcessingBuilder) { + let mut builder = BlockProcessingBuilder::new(VALIDATOR_COUNT, &spec); + + // Set the state and block to be in the last slot of the 4th epoch. + let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); + builder.set_slot(last_slot_of_epoch, &spec); + builder.build_caches(&spec); + + (builder) +} diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 18f57c5892..9dca6222a6 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -23,6 +23,11 @@ impl TestingBeaconBlockBuilder { } } + /// Set the previous block root + pub fn set_previous_block_root(&mut self, root: Hash256) { + self.block.previous_block_root = root; + } + /// Set the slot of the block. pub fn set_slot(&mut self, slot: Slot) { self.block.slot = slot; @@ -48,6 +53,11 @@ impl TestingBeaconBlockBuilder { self.block.body.randao_reveal = Signature::new(&message, domain, sk); } + /// Has the randao reveal been set? + pub fn randao_reveal_not_set(&mut self) -> bool { + self.block.body.randao_reveal.is_empty() + } + /// Inserts a signed, valid `ProposerSlashing` for the validator. pub fn insert_proposer_slashing( &mut self, diff --git a/eth2/utils/bls/src/fake_signature.rs b/eth2/utils/bls/src/fake_signature.rs index 0bf3d0a257..de16a05f38 100644 --- a/eth2/utils/bls/src/fake_signature.rs +++ b/eth2/utils/bls/src/fake_signature.rs @@ -14,6 +14,7 @@ use tree_hash::tree_hash_ssz_encoding_as_vector; #[derive(Debug, PartialEq, Clone, Eq)] pub struct FakeSignature { bytes: Vec, + is_empty: bool, } impl FakeSignature { @@ -26,6 +27,7 @@ impl FakeSignature { pub fn zero() -> Self { Self { bytes: vec![0; BLS_SIG_BYTE_SIZE], + is_empty: true, } } @@ -59,6 +61,7 @@ impl FakeSignature { } else { Ok(Self { bytes: bytes.to_vec(), + is_empty: false, }) } } @@ -71,6 +74,11 @@ impl FakeSignature { pub fn empty_signature() -> Self { FakeSignature::zero() } + + // Check for empty Signature + pub fn is_empty(&self) -> bool { + self.is_empty + } } impl_ssz!(FakeSignature, BLS_SIG_BYTE_SIZE, "FakeSignature");