diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index cf368be374..0abe4596be 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -28,6 +28,7 @@ sloggers = "0.3.4" slot_clock = { path = "../../eth2/utils/slot_clock" } eth2_hashing = "0.1.0" eth2_ssz = "0.1.2" +eth2_ssz_types = { path = "../../eth2/utils/ssz_types" } eth2_ssz_derive = "0.1.0" state_processing = { path = "../../eth2/state_processing" } tree_hash = "0.1.0" diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6df72c9981..1992503d39 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -444,6 +444,42 @@ impl BeaconChain { } } + /// Produce an `Attestation` that is valid for the given `slot` `shard`. + /// + /// Always attests to the canonical chain. + pub fn produce_attestation( + &self, + shard: u64, + slot: Slot, + ) -> Result, Error> { + let state = self.state_at_slot(slot)?; + let head = self.head(); + + let data = self.produce_attestation_data_for_block( + shard, + head.beacon_block_root, + head.beacon_block.slot, + &state, + )?; + + let relative_epoch = + RelativeEpoch::from_slot(state.slot, slot, T::EthSpec::slots_per_epoch())?; + + let committee_len = state + .get_crosslink_committee_for_shard(shard, relative_epoch)? + .committee + .len(); + + let empty_bitfield = BitList::with_capacity(committee_len)?; + + Ok(Attestation { + aggregation_bits: empty_bitfield.clone(), + data, + custody_bits: empty_bitfield, + signature: AggregateSignature::new(), + }) + } + /// Produce an `AttestationData` that is valid for the given `slot` `shard`. /// /// Always attests to the canonical chain. diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index f8046980fd..9877bc24e0 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -1,5 +1,6 @@ use crate::eth1_chain::Error as Eth1ChainError; use crate::fork_choice::Error as ForkChoiceError; +use ssz_types::Error as SszTypesError; use state_processing::per_block_processing::errors::AttestationValidationError; use state_processing::BlockProcessingError; use state_processing::SlotProcessingError; @@ -41,10 +42,14 @@ pub enum BeaconChainError { AttestationValidationError(AttestationValidationError), /// Returned when an internal check fails, indicating corrupt data. InvariantViolated(String), + RelativeEpochError(RelativeEpochError), + SszTypesError(SszTypesError), } easy_from_to!(SlotProcessingError, BeaconChainError); easy_from_to!(AttestationValidationError, BeaconChainError); +easy_from_to!(RelativeEpochError, BeaconChainError); +easy_from_to!(SszTypesError, BeaconChainError); #[derive(Debug, PartialEq)] pub enum BlockProductionError { diff --git a/beacon_node/rest_api/src/helpers.rs b/beacon_node/rest_api/src/helpers.rs index d6b50b5ecb..7c675e50f9 100644 --- a/beacon_node/rest_api/src/helpers.rs +++ b/beacon_node/rest_api/src/helpers.rs @@ -38,6 +38,15 @@ pub fn parse_epoch(string: &str) -> Result { .map_err(|e| ApiError::BadRequest(format!("Unable to parse epoch: {:?}", e))) } +/// Parse an shard. +/// +/// E.g., `"18"` +pub fn parse_shard(string: &str) -> Result { + string + .parse::() + .map_err(|e| ApiError::BadRequest(format!("Unable to parse shard: {:?}", e))) +} + /// Checks the provided request to ensure that the `content-type` header. /// /// The content-type header should either be omitted, in which case JSON is assumed, or it should diff --git a/beacon_node/rest_api/src/validator.rs b/beacon_node/rest_api/src/validator.rs index 0cba9ee30a..4cac918857 100644 --- a/beacon_node/rest_api/src/validator.rs +++ b/beacon_node/rest_api/src/validator.rs @@ -1,6 +1,6 @@ use crate::helpers::{ - check_content_type_for_json, parse_epoch, parse_pubkey, parse_signature, - publish_attestation_to_network, publish_beacon_block_to_network, + check_content_type_for_json, parse_epoch, parse_pubkey, parse_shard, parse_signature, + parse_slot, publish_attestation_to_network, publish_beacon_block_to_network, }; use crate::response_builder::ResponseBuilder; use crate::{ApiError, ApiResult, BoxFut, NetworkChannel, UrlQuery}; @@ -82,44 +82,43 @@ pub fn get_validator_duties( let duties = query .all_of("validator_pubkeys")? .iter() - .map(|string| parse_pubkey(string)) - .collect::, _>>()? - .into_iter() - .map(|validator_pubkey| { - if let Some(validator_index) = head_state - .get_validator_index(&validator_pubkey) - .map_err(|e| { - ApiError::ServerError(format!("Unable to read pubkey cache: {:?}", e)) - })? - { - let duties = head_state - .get_attestation_duties(validator_index, relative_epoch) + .map(|validator_pubkey_str| { + parse_pubkey(validator_pubkey_str).and_then(|validator_pubkey| { + if let Some(validator_index) = head_state + .get_validator_index(&validator_pubkey) .map_err(|e| { - ApiError::ServerError(format!( - "Unable to obtain attestation duties: {:?}", - e - )) - })?; + ApiError::ServerError(format!("Unable to read pubkey cache: {:?}", e)) + })? + { + let duties = head_state + .get_attestation_duties(validator_index, relative_epoch) + .map_err(|e| { + ApiError::ServerError(format!( + "Unable to obtain attestation duties: {:?}", + e + )) + })?; - let block_proposal_slot = validator_proposers - .iter() - .find(|(i, _slot)| validator_index == *i) - .map(|(_i, slot)| *slot); + let block_proposal_slot = validator_proposers + .iter() + .find(|(i, _slot)| validator_index == *i) + .map(|(_i, slot)| *slot); - Ok(ValidatorDuty { - validator_pubkey, - attestation_slot: duties.map(|d| d.slot), - attestation_shard: duties.map(|d| d.shard), - block_proposal_slot, - }) - } else { - Ok(ValidatorDuty { - validator_pubkey, - attestation_slot: None, - attestation_shard: None, - block_proposal_slot: None, - }) - } + Ok(ValidatorDuty { + validator_pubkey, + attestation_slot: duties.map(|d| d.slot), + attestation_shard: duties.map(|d| d.shard), + block_proposal_slot, + }) + } else { + Ok(ValidatorDuty { + validator_pubkey, + attestation_slot: None, + attestation_shard: None, + block_proposal_slot: None, + }) + } + }) }) .collect::, ApiError>>()?;