Use all attestation subnets (#1257)

* Update `milagro_bls` to new release (#1183)

* Update milagro_bls to new release

Signed-off-by: Kirk Baird <baird.k@outlook.com>

* Tidy up fake cryptos

Signed-off-by: Kirk Baird <baird.k@outlook.com>

* move SecretHash to bls and put plaintext back

Signed-off-by: Kirk Baird <baird.k@outlook.com>

* Update v0.12.0 to v0.12.1

* Add compute_subnet_for_attestation

* Replace CommitteeIndex topic with Attestation

* Fix warnings

* Fix attestation service tests

* fmt

* Appease clippy

* return error from validator_subscriptions

* move state out of loop

* Fix early break on error

* Get state from slot clock

* Fix beacon state in attestation tests

* Add failing test for lookahead > 1

* Minor change

* Address some review comments

* Add subnet verification to beacon chain

* Move subnet verification to processor

* Pass committee_count_at_slot to ValidatorDuty and ValidatorSubscription

* Pass subnet id for publishing attestations

* Fix attestation service tests

* Fix more tests

* Fix fork choice test

* Remove unused code

* Remove more unused and expensive code

Co-authored-by: Kirk Baird <baird.k@outlook.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
Co-authored-by: Age Manning <Age@AgeManning.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
Pawan Dhananjay 2020-06-18 14:41:03 +05:30 committed by GitHub
parent 81c9fe3817
commit 3199b1a6f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 444 additions and 199 deletions

View File

@ -52,7 +52,7 @@ use std::borrow::Cow;
use tree_hash::TreeHash;
use types::{
Attestation, BeaconCommittee, CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation,
RelativeEpoch, SelectionProof, SignedAggregateAndProof, Slot,
RelativeEpoch, SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
};
/// Returned when an attestation was not successfully verified. It might not have been verified for
@ -123,6 +123,11 @@ pub enum Error {
/// The attestation is attesting to a state that is later than itself. (Viz., attesting to the
/// future).
AttestsToFutureBlock { block: Slot, attestation: Slot },
/// The attestation was received on an invalid attestation subnet.
InvalidSubnetId {
received: SubnetId,
expected: SubnetId,
},
/// The attestation failed the `state_processing` verification stage.
Invalid(AttestationValidationError),
/// There was an error whilst processing the attestation. It is not known if it is valid or invalid.
@ -234,28 +239,29 @@ impl<T: BeaconChainTypes> VerifiedAggregatedAttestation<T> {
return Err(Error::EmptyAggregationBitfield);
}
let indexed_attestation = map_attestation_committee(chain, attestation, |committee| {
// Note: this clones the signature which is known to be a relatively slow operation.
//
// Future optimizations should remove this clone.
let selection_proof =
SelectionProof::from(signed_aggregate.message.selection_proof.clone());
let indexed_attestation =
map_attestation_committee(chain, attestation, |(committee, _)| {
// Note: this clones the signature which is known to be a relatively slow operation.
//
// Future optimizations should remove this clone.
let selection_proof =
SelectionProof::from(signed_aggregate.message.selection_proof.clone());
if !selection_proof
.is_aggregator(committee.committee.len(), &chain.spec)
.map_err(|e| Error::BeaconChainError(e.into()))?
{
return Err(Error::InvalidSelectionProof { aggregator_index });
}
if !selection_proof
.is_aggregator(committee.committee.len(), &chain.spec)
.map_err(|e| Error::BeaconChainError(e.into()))?
{
return Err(Error::InvalidSelectionProof { aggregator_index });
}
// Ensure the aggregator is a member of the committee for which it is aggregating.
if !committee.committee.contains(&(aggregator_index as usize)) {
return Err(Error::AggregatorNotInCommittee { aggregator_index });
}
// Ensure the aggregator is a member of the committee for which it is aggregating.
if !committee.committee.contains(&(aggregator_index as usize)) {
return Err(Error::AggregatorNotInCommittee { aggregator_index });
}
get_indexed_attestation(committee.committee, &attestation)
.map_err(|e| BeaconChainError::from(e).into())
})?;
get_indexed_attestation(committee.committee, &attestation)
.map_err(|e| BeaconChainError::from(e).into())
})?;
// Ensure that all signatures are valid.
if !verify_signed_aggregate_signatures(chain, &signed_aggregate, &indexed_attestation)? {
@ -309,8 +315,12 @@ impl<T: BeaconChainTypes> VerifiedAggregatedAttestation<T> {
impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
/// Returns `Ok(Self)` if the `attestation` is valid to be (re)published on the gossip
/// network.
///
/// `subnet_id` is the subnet from which we received this attestation. This function will
/// verify that it was received on the correct subnet.
pub fn verify(
attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
chain: &BeaconChain<T>,
) -> Result<Self, Error> {
// Ensure attestation is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a
@ -330,7 +340,23 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<T> {
// attestation and do not delay consideration for later.
verify_head_block_is_known(chain, &attestation)?;
let indexed_attestation = obtain_indexed_attestation(chain, &attestation)?;
let (indexed_attestation, committees_per_slot) =
obtain_indexed_attestation_and_committees_per_slot(chain, &attestation)?;
let expected_subnet_id = SubnetId::compute_subnet_for_attestation_data::<T::EthSpec>(
&indexed_attestation.data,
committees_per_slot,
&chain.spec,
)
.map_err(BeaconChainError::from)?;
// Ensure the attestation is from the correct subnet.
if subnet_id != expected_subnet_id {
return Err(Error::InvalidSubnetId {
received: subnet_id,
expected: expected_subnet_id,
});
}
let validator_index = *indexed_attestation
.attesting_indices
@ -566,19 +592,23 @@ pub fn verify_signed_aggregate_signatures<T: BeaconChainTypes>(
Ok(verify_signature_sets(signature_sets))
}
/// Returns the `indexed_attestation` for the `attestation` using the public keys cached in the
/// `chain`.
pub fn obtain_indexed_attestation<T: BeaconChainTypes>(
/// Assists in readability.
type CommitteesPerSlot = u64;
/// Returns the `indexed_attestation` and committee count per slot for the `attestation` using the
/// public keys cached in the `chain`.
pub fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
attestation: &Attestation<T::EthSpec>,
) -> Result<IndexedAttestation<T::EthSpec>, Error> {
map_attestation_committee(chain, attestation, |committee| {
) -> Result<(IndexedAttestation<T::EthSpec>, CommitteesPerSlot), Error> {
map_attestation_committee(chain, attestation, |(committee, committees_per_slot)| {
get_indexed_attestation(committee.committee, &attestation)
.map(|attestation| (attestation, committees_per_slot))
.map_err(|e| BeaconChainError::from(e).into())
})
}
/// Runs the `map_fn` with the committee for the given `attestation`.
/// Runs the `map_fn` with the committee and committee count per slot for the given `attestation`.
///
/// This function exists in this odd "map" pattern because efficiently obtaining the committee for
/// an attestation can be complex. It might involve reading straight from the
@ -594,7 +624,7 @@ pub fn map_attestation_committee<'a, T, F, R>(
) -> Result<R, Error>
where
T: BeaconChainTypes,
F: Fn(BeaconCommittee) -> Result<R, Error>,
F: Fn((BeaconCommittee, CommitteesPerSlot)) -> Result<R, Error>,
{
let attestation_epoch = attestation.data.slot.epoch(T::EthSpec::slots_per_epoch());
let target = &attestation.data.target;
@ -624,9 +654,10 @@ where
metrics::stop_timer(cache_wait_timer);
if let Some(committee_cache) = shuffling_cache.get(attestation_epoch, target.root) {
let committees_per_slot = committee_cache.committees_per_slot();
committee_cache
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.map(map_fn)
.map(|committee| map_fn((committee, committees_per_slot)))
.unwrap_or_else(|| {
Err(Error::NoCommitteeForSlotAndIndex {
slot: attestation.data.slot,
@ -689,9 +720,10 @@ where
metrics::stop_timer(committee_building_timer);
let committees_per_slot = committee_cache.committees_per_slot();
committee_cache
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.map(map_fn)
.map(|committee| map_fn((committee, committees_per_slot)))
.unwrap_or_else(|| {
Err(Error::NoCommitteeForSlotAndIndex {
slot: attestation.data.slot,

View File

@ -872,12 +872,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn verify_unaggregated_attestation_for_gossip(
&self,
attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
) -> Result<VerifiedUnaggregatedAttestation<T>, AttestationError> {
metrics::inc_counter(&metrics::UNAGGREGATED_ATTESTATION_PROCESSING_REQUESTS);
let _timer =
metrics::start_timer(&metrics::UNAGGREGATED_ATTESTATION_GOSSIP_VERIFICATION_TIMES);
VerifiedUnaggregatedAttestation::verify(attestation, self).map(|v| {
VerifiedUnaggregatedAttestation::verify(attestation, subnet_id, self).map(|v| {
metrics::inc_counter(&metrics::UNAGGREGATED_ATTESTATION_PROCESSING_SUCCESSES);
v
})

View File

@ -24,7 +24,7 @@ use tree_hash::TreeHash;
use types::{
AggregateSignature, Attestation, BeaconState, BeaconStateHash, ChainSpec, Domain, EthSpec,
Hash256, Keypair, SecretKey, SelectionProof, Signature, SignedAggregateAndProof,
SignedBeaconBlock, SignedBeaconBlockHash, SignedRoot, Slot,
SignedBeaconBlock, SignedBeaconBlockHash, SignedRoot, Slot, SubnetId,
};
pub use types::test_utils::generate_deterministic_keypairs;
@ -536,12 +536,16 @@ where
state: &BeaconState<E>,
head_block_root: Hash256,
attestation_slot: Slot,
) -> Vec<Vec<Attestation<E>>> {
) -> Vec<Vec<(Attestation<E>, SubnetId)>> {
let spec = &self.spec;
let fork = &state.fork;
let attesting_validators = self.get_attesting_validators(attestation_strategy);
let committee_count = state
.get_committee_count_at_slot(state.slot)
.expect("should get committee count");
state
.get_beacon_committees_at_slot(state.slot)
.expect("should get committees")
@ -589,7 +593,14 @@ where
agg_sig
};
Some(attestation)
let subnet_id = SubnetId::compute_subnet_for_attestation_data::<E>(
&attestation.data,
committee_count,
&self.chain.spec,
)
.expect("should get subnet_id");
Some((attestation, subnet_id))
})
.collect()
})
@ -634,16 +645,16 @@ where
.into_iter()
.for_each(|committee_attestations| {
// Submit each unaggregated attestation to the chain.
for attestation in &committee_attestations {
for (attestation, subnet_id) in &committee_attestations {
self.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone())
.verify_unaggregated_attestation_for_gossip(attestation.clone(), *subnet_id)
.expect("should not error during attestation processing")
.add_to_pool(&self.chain)
.expect("should add attestation to naive pool");
}
// If there are any attestations in this committee, create an aggregate.
if let Some(attestation) = committee_attestations.first() {
if let Some((attestation, _)) = committee_attestations.first() {
let bc = state.get_beacon_committee(attestation.data.slot, attestation.data.index)
.expect("should get committee");
@ -677,7 +688,7 @@ where
.get_aggregated_attestation(&attestation.data)
.expect("should not error whilst finding aggregate")
.unwrap_or_else(|| {
committee_attestations.iter().skip(1).fold(attestation.clone(), |mut agg, att| {
committee_attestations.iter().skip(1).fold(attestation.clone(), |mut agg, (att, _)| {
agg.aggregate(att);
agg
})

View File

@ -14,7 +14,7 @@ use tree_hash::TreeHash;
use types::{
test_utils::generate_deterministic_keypair, AggregateSignature, Attestation, EthSpec, Hash256,
Keypair, MainnetEthSpec, SecretKey, SelectionProof, Signature, SignedAggregateAndProof,
SignedBeaconBlock, Unsigned,
SignedBeaconBlock, SubnetId, Unsigned,
};
pub type E = MainnetEthSpec;
@ -49,7 +49,7 @@ fn get_harness(validator_count: usize) -> BeaconChainHarness<HarnessType<E>> {
/// Also returns some info about who created it.
fn get_valid_unaggregated_attestation<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
) -> (Attestation<T::EthSpec>, usize, usize, SecretKey) {
) -> (Attestation<T::EthSpec>, usize, usize, SecretKey, SubnetId) {
let head = chain.head().expect("should get head");
let current_slot = chain.slot().expect("should get slot");
@ -78,11 +78,21 @@ fn get_valid_unaggregated_attestation<T: BeaconChainTypes>(
)
.expect("should sign attestation");
let subnet_id = SubnetId::compute_subnet_for_attestation_data::<E>(
&valid_attestation.data,
head.beacon_state
.get_committee_count_at_slot(current_slot)
.expect("should get committee count"),
&chain.spec,
)
.expect("should get subnet_id");
(
valid_attestation,
validator_index,
validator_committee_index,
validator_sk,
subnet_id,
)
}
@ -194,7 +204,7 @@ fn aggregated_gossip_verification() {
"the test requires a new epoch to avoid already-seen errors"
);
let (valid_attestation, _attester_index, _attester_committee_index, validator_sk) =
let (valid_attestation, _attester_index, _attester_committee_index, validator_sk, _subnet_id) =
get_valid_unaggregated_attestation(&harness.chain);
let (valid_aggregate, aggregator_index, aggregator_sk) =
get_valid_aggregated_attestation(&harness.chain, valid_attestation);
@ -541,16 +551,21 @@ fn unaggregated_gossip_verification() {
"the test requires a new epoch to avoid already-seen errors"
);
let (valid_attestation, expected_validator_index, validator_committee_index, validator_sk) =
get_valid_unaggregated_attestation(&harness.chain);
let (
valid_attestation,
expected_validator_index,
validator_committee_index,
validator_sk,
subnet_id,
) = get_valid_unaggregated_attestation(&harness.chain);
macro_rules! assert_invalid {
($desc: tt, $attn_getter: expr, $($error: pat) |+ $( if $guard: expr )?) => {
($desc: tt, $attn_getter: expr, $subnet_getter: expr, $($error: pat) |+ $( if $guard: expr )?) => {
assert!(
matches!(
harness
.chain
.verify_unaggregated_attestation_for_gossip($attn_getter)
.verify_unaggregated_attestation_for_gossip($attn_getter, $subnet_getter)
.err()
.expect(&format!(
"{} should error during verify_unaggregated_attestation_for_gossip",
@ -564,6 +579,29 @@ fn unaggregated_gossip_verification() {
};
}
/*
* The following test ensures:
*
* Spec v0.12.1
*
* The attestation is for the correct subnet (i.e. compute_subnet_for_attestation(state,
* attestation.data.slot, attestation.data.index) == subnet_id).
*/
let id: u64 = subnet_id.into();
let invalid_subnet_id = SubnetId::new(id + 1);
assert_invalid!(
"attestation from future slot",
{
valid_attestation.clone()
},
invalid_subnet_id,
AttnError::InvalidSubnetId {
received,
expected,
}
if received == invalid_subnet_id && expected == subnet_id
);
/*
* The following two tests ensure:
*
@ -583,6 +621,7 @@ fn unaggregated_gossip_verification() {
a.data.slot = future_slot;
a
},
subnet_id,
AttnError::FutureSlot {
attestation_slot,
latest_permissible_slot,
@ -602,6 +641,7 @@ fn unaggregated_gossip_verification() {
a.data.slot = early_slot;
a
},
subnet_id,
AttnError::PastSlot {
attestation_slot,
// Subtract an additional slot since the harness will be exactly on the start of the
@ -634,6 +674,7 @@ fn unaggregated_gossip_verification() {
);
a
},
subnet_id,
AttnError::NotExactlyOneAggregationBitSet(0)
);
@ -646,6 +687,7 @@ fn unaggregated_gossip_verification() {
.expect("should set second aggregation bit");
a
},
subnet_id,
AttnError::NotExactlyOneAggregationBitSet(2)
);
@ -665,6 +707,7 @@ fn unaggregated_gossip_verification() {
a.data.beacon_block_root = unknown_root;
a
},
subnet_id,
AttnError::UnknownHeadBlock {
beacon_block_root,
}
@ -690,13 +733,14 @@ fn unaggregated_gossip_verification() {
a
},
subnet_id,
AttnError::InvalidSignature
);
assert!(
harness
.chain
.verify_unaggregated_attestation_for_gossip(valid_attestation.clone())
.verify_unaggregated_attestation_for_gossip(valid_attestation.clone(), subnet_id)
.is_ok(),
"valid attestation should be verified"
);
@ -714,6 +758,7 @@ fn unaggregated_gossip_verification() {
assert_invalid!(
"attestation that has already been seen",
valid_attestation.clone(),
subnet_id,
AttnError::PriorAttestationKnown {
validator_index,
epoch,
@ -755,7 +800,7 @@ fn attestation_that_skips_epochs() {
per_slot_processing(&mut state, None, &harness.spec).expect("should process slot");
}
let attestation = harness
let (attestation, subnet_id) = harness
.get_unaggregated_attestations(
&AttestationStrategy::AllValidators,
&state,
@ -785,6 +830,6 @@ fn attestation_that_skips_epochs() {
harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation)
.verify_unaggregated_attestation_for_gossip(attestation, subnet_id)
.expect("should gossip verify attestation that skips slots");
}

View File

@ -293,7 +293,7 @@ fn epoch_boundary_state_attestation_processing() {
let mut checked_pre_fin = false;
for attestation in late_attestations.into_iter().flatten() {
for (attestation, subnet_id) in late_attestations.into_iter().flatten() {
// load_epoch_boundary_state is idempotent!
let block_root = attestation.data.beacon_block_root;
let block = store.get_block(&block_root).unwrap().expect("block exists");
@ -317,7 +317,7 @@ fn epoch_boundary_state_attestation_processing() {
let res = harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone());
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id);
let current_slot = harness.chain.slot().expect("should get slot");
let expected_attestation_slot = attestation.data.slot;

View File

@ -459,10 +459,10 @@ fn attestations_with_increasing_slots() {
harness.advance_slot();
}
for attestation in attestations.into_iter().flatten() {
for (attestation, subnet_id) in attestations.into_iter().flatten() {
let res = harness
.chain
.verify_unaggregated_attestation_for_gossip(attestation.clone());
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id);
let current_slot = harness.chain.slot().expect("should get slot");
let expected_attestation_slot = attestation.data.slot;

View File

@ -41,7 +41,7 @@ impl<T: EthSpec> PubsubMessage<T> {
PubsubMessage::BeaconBlock(_) => GossipKind::BeaconBlock,
PubsubMessage::AggregateAndProofAttestation(_) => GossipKind::BeaconAggregateAndProof,
PubsubMessage::Attestation(attestation_data) => {
GossipKind::CommitteeIndex(attestation_data.0)
GossipKind::Attestation(attestation_data.0)
}
PubsubMessage::VoluntaryExit(_) => GossipKind::VoluntaryExit,
PubsubMessage::ProposerSlashing(_) => GossipKind::ProposerSlashing,
@ -97,7 +97,7 @@ impl<T: EthSpec> PubsubMessage<T> {
agg_and_proof,
)));
}
GossipKind::CommitteeIndex(subnet_id) => {
GossipKind::Attestation(subnet_id) => {
let attestation = Attestation::from_ssz_bytes(decompressed_data)
.map_err(|e| format!("{:?}", e))?;
return Ok(PubsubMessage::Attestation(Box::new((

View File

@ -9,10 +9,7 @@ pub const TOPIC_PREFIX: &str = "eth2";
pub const SSZ_SNAPPY_ENCODING_POSTFIX: &str = "ssz_snappy";
pub const BEACON_BLOCK_TOPIC: &str = "beacon_block";
pub const BEACON_AGGREGATE_AND_PROOF_TOPIC: &str = "beacon_aggregate_and_proof";
// for speed and easier string manipulation, committee topic index is split into a prefix and a
// postfix. The topic is committee_index{}_beacon_attestation where {} is an integer.
pub const COMMITEE_INDEX_TOPIC_PREFIX: &str = "committee_index";
pub const COMMITEE_INDEX_TOPIC_POSTFIX: &str = "_beacon_attestation";
pub const BEACON_ATTESTATION_PREFIX: &str = "beacon_attestation_";
pub const VOLUNTARY_EXIT_TOPIC: &str = "voluntary_exit";
pub const PROPOSER_SLASHING_TOPIC: &str = "proposer_slashing";
pub const ATTESTER_SLASHING_TOPIC: &str = "attester_slashing";
@ -38,7 +35,7 @@ pub enum GossipKind {
/// Topic for publishing aggregate attestations and proofs.
BeaconAggregateAndProof,
/// Topic for publishing raw attestations on a particular subnet.
CommitteeIndex(SubnetId),
Attestation(SubnetId),
/// Topic for publishing voluntary exits.
VoluntaryExit,
/// Topic for publishing block proposer slashings.
@ -52,7 +49,7 @@ impl std::fmt::Display for GossipKind {
match self {
GossipKind::BeaconBlock => write!(f, "beacon_block"),
GossipKind::BeaconAggregateAndProof => write!(f, "beacon_aggregate_and_proof"),
GossipKind::CommitteeIndex(subnet_id) => write!(f, "committee_index_{}", **subnet_id),
GossipKind::Attestation(subnet_id) => write!(f, "beacon_attestation_{}", **subnet_id),
GossipKind::VoluntaryExit => write!(f, "voluntary_exit"),
GossipKind::ProposerSlashing => write!(f, "proposer_slashing"),
GossipKind::AttesterSlashing => write!(f, "attester_slashing"),
@ -124,7 +121,7 @@ impl GossipTopic {
PROPOSER_SLASHING_TOPIC => GossipKind::ProposerSlashing,
ATTESTER_SLASHING_TOPIC => GossipKind::AttesterSlashing,
topic => match committee_topic_index(topic) {
Some(subnet_id) => GossipKind::CommitteeIndex(subnet_id),
Some(subnet_id) => GossipKind::Attestation(subnet_id),
None => return Err(format!("Unknown topic: {}", topic)),
},
};
@ -158,10 +155,7 @@ impl Into<String> for GossipTopic {
GossipKind::VoluntaryExit => VOLUNTARY_EXIT_TOPIC.into(),
GossipKind::ProposerSlashing => PROPOSER_SLASHING_TOPIC.into(),
GossipKind::AttesterSlashing => ATTESTER_SLASHING_TOPIC.into(),
GossipKind::CommitteeIndex(index) => format!(
"{}{}{}",
COMMITEE_INDEX_TOPIC_PREFIX, *index, COMMITEE_INDEX_TOPIC_POSTFIX
),
GossipKind::Attestation(index) => format!("{}{}", BEACON_ATTESTATION_PREFIX, *index,),
};
format!(
"/{}/{}/{}/{}",
@ -175,7 +169,7 @@ impl Into<String> for GossipTopic {
impl From<SubnetId> for GossipKind {
fn from(subnet_id: SubnetId) -> Self {
GossipKind::CommitteeIndex(subnet_id)
GossipKind::Attestation(subnet_id)
}
}
@ -183,17 +177,9 @@ impl From<SubnetId> for GossipKind {
// Determines if a string is a committee topic.
fn committee_topic_index(topic: &str) -> Option<SubnetId> {
if topic.starts_with(COMMITEE_INDEX_TOPIC_PREFIX)
&& topic.ends_with(COMMITEE_INDEX_TOPIC_POSTFIX)
{
if topic.starts_with(BEACON_ATTESTATION_PREFIX) {
return Some(SubnetId::new(
u64::from_str_radix(
topic
.trim_start_matches(COMMITEE_INDEX_TOPIC_PREFIX)
.trim_end_matches(COMMITEE_INDEX_TOPIC_POSTFIX),
10,
)
.ok()?,
u64::from_str_radix(topic.trim_start_matches(BEACON_ATTESTATION_PREFIX), 10).ok()?,
));
}
None

View File

@ -3,12 +3,12 @@
//! determines whether attestations should be aggregated and/or passed to the beacon node.
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2_libp2p::{types::GossipKind, MessageId, NetworkGlobals, PeerId};
use eth2_libp2p::{types::GossipKind, NetworkGlobals};
use futures::prelude::*;
use hashset_delay::HashSetDelay;
use rand::seq::SliceRandom;
use rest_types::ValidatorSubscription;
use slog::{crit, debug, error, o, warn};
use slog::{crit, debug, error, o, trace, warn};
use slot_clock::SlotClock;
use std::collections::VecDeque;
use std::pin::Pin;
@ -186,18 +186,34 @@ impl<T: BeaconChainTypes> AttestationService<T> {
pub fn validator_subscriptions(
&mut self,
subscriptions: Vec<ValidatorSubscription>,
) -> Result<(), ()> {
) -> Result<(), String> {
for subscription in subscriptions {
//NOTE: We assume all subscriptions have been verified before reaching this service
// Registers the validator with the attestation service.
// This will subscribe to long-lived random subnets if required.
trace!(self.log,
"Validator subscription";
"subscription" => format!("{:?}", subscription),
);
self.add_known_validator(subscription.validator_index);
let subnet_id = SubnetId::new(
subscription.attestation_committee_index
% self.beacon_chain.spec.attestation_subnet_count,
);
let subnet_id = match SubnetId::compute_subnet::<T::EthSpec>(
subscription.slot,
subscription.attestation_committee_index,
subscription.committee_count_at_slot,
&self.beacon_chain.spec,
) {
Ok(subnet_id) => subnet_id,
Err(e) => {
warn!(self.log,
"Failed to compute subnet id for validator subscription";
"error" => format!("{:?}", e),
"validator_index" => subscription.validator_index
);
continue;
}
};
let exact_subnet = ExactSubnet {
subnet_id,
@ -219,9 +235,18 @@ impl<T: BeaconChainTypes> AttestationService<T> {
if subscription.is_aggregator {
// set the subscription timer to subscribe to the next subnet if required
if let Err(e) = self.subscribe_to_subnet(exact_subnet) {
warn!(self.log, "Subscription to subnet error"; "error" => e);
return Err(());
if let Err(e) = self.subscribe_to_subnet(exact_subnet.clone()) {
warn!(self.log,
"Subscription to subnet error";
"error" => e,
"validator_index" => subscription.validator_index,
);
} else {
trace!(self.log,
"Subscribed to subnet for aggregator duties";
"exact_subnet" => format!("{:?}", exact_subnet),
"validator_index" => subscription.validator_index
);
}
}
}
@ -232,25 +257,9 @@ impl<T: BeaconChainTypes> AttestationService<T> {
/// verification, re-propagates and returns false.
pub fn should_process_attestation(
&mut self,
_message_id: &MessageId,
peer_id: &PeerId,
subnet: &SubnetId,
attestation: &Attestation<T::EthSpec>,
) -> bool {
// verify the attestation is on the correct subnet
let expected_subnet = match attestation.subnet_id(&self.beacon_chain.spec) {
Ok(v) => v,
Err(e) => {
warn!(self.log, "Could not obtain attestation subnet_id"; "error" => format!("{:?}", e));
return false;
}
};
if expected_subnet != *subnet {
warn!(self.log, "Received an attestation on the wrong subnet"; "subnet_received" => format!("{:?}", subnet), "subnet_expected" => format!("{:?}",expected_subnet), "peer_id" => format!("{}", peer_id));
return false;
}
let exact_subnet = ExactSubnet {
subnet_id: subnet.clone(),
slot: attestation.data.slot,
@ -511,7 +520,7 @@ impl<T: BeaconChainTypes> AttestationService<T> {
self.random_subnets.insert(subnet_id);
// if we are not already subscribed, then subscribe
let topic_kind = &GossipKind::CommitteeIndex(subnet_id);
let topic_kind = &GossipKind::Attestation(subnet_id);
let already_subscribed = self
.network_globals
@ -574,7 +583,7 @@ impl<T: BeaconChainTypes> AttestationService<T> {
// we are also not un-subscribing from a subnet if the next slot requires us to be
// subscribed. Therefore there could be the case that we are already still subscribed
// to the required subnet. In which case we do not issue another subscription request.
let topic_kind = &GossipKind::CommitteeIndex(exact_subnet.subnet_id);
let topic_kind = &GossipKind::Attestation(exact_subnet.subnet_id);
if self
.network_globals
.gossipsub_subscriptions

View File

@ -108,17 +108,23 @@ mod tests {
validator_index: u64,
attestation_committee_index: CommitteeIndex,
slot: Slot,
committee_count_at_slot: u64,
) -> ValidatorSubscription {
let is_aggregator = true;
ValidatorSubscription {
validator_index,
attestation_committee_index,
slot,
committee_count_at_slot,
is_aggregator,
}
}
fn _get_subscriptions(validator_count: u64, slot: Slot) -> Vec<ValidatorSubscription> {
fn _get_subscriptions(
validator_count: u64,
slot: Slot,
committee_count_at_slot: u64,
) -> Vec<ValidatorSubscription> {
let mut subscriptions: Vec<ValidatorSubscription> = Vec::new();
for validator_index in 0..validator_count {
let is_aggregator = true;
@ -126,6 +132,7 @@ mod tests {
validator_index,
attestation_committee_index: validator_index,
slot,
committee_count_at_slot,
is_aggregator,
});
}
@ -167,6 +174,7 @@ mod tests {
let committee_index = 1;
let subscription_slot = 0;
let no_events_expected = 4;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -180,6 +188,7 @@ mod tests {
validator_index,
committee_index,
current_slot + Slot::new(subscription_slot),
committee_count,
)];
// submit the subscriptions
@ -188,7 +197,15 @@ mod tests {
.unwrap();
// not enough time for peer discovery, just subscribe
let expected = vec![AttServiceMessage::Subscribe(SubnetId::new(validator_index))];
let expected = vec![AttServiceMessage::Subscribe(
SubnetId::compute_subnet::<MinimalEthSpec>(
current_slot + Slot::new(subscription_slot),
committee_index,
committee_count,
&attestation_service.beacon_chain.spec,
)
.unwrap(),
)];
let events = get_events(attestation_service, no_events_expected, 1).await;
assert_matches!(
@ -215,6 +232,7 @@ mod tests {
let committee_index = 1;
let subscription_slot = 0;
let no_events_expected = 5;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -228,6 +246,7 @@ mod tests {
validator_index,
committee_index,
current_slot + Slot::new(subscription_slot),
committee_count,
)];
// submit the subscriptions
@ -236,9 +255,16 @@ mod tests {
.unwrap();
// not enough time for peer discovery, just subscribe, unsubscribe
let subnet_id = SubnetId::compute_subnet::<MinimalEthSpec>(
current_slot + Slot::new(subscription_slot),
committee_index,
committee_count,
&attestation_service.beacon_chain.spec,
)
.unwrap();
let expected = vec![
AttServiceMessage::Subscribe(SubnetId::new(validator_index)),
AttServiceMessage::Unsubscribe(SubnetId::new(validator_index)),
AttServiceMessage::Subscribe(subnet_id),
AttServiceMessage::Unsubscribe(subnet_id),
];
let events = get_events(attestation_service, no_events_expected, 2).await;
@ -266,6 +292,7 @@ mod tests {
let committee_index = 1;
let subscription_slot = 5;
let no_events_expected = 4;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -279,6 +306,7 @@ mod tests {
validator_index,
committee_index,
current_slot + Slot::new(subscription_slot),
committee_count,
)];
// submit the subscriptions
@ -295,10 +323,14 @@ mod tests {
);
// just discover peers, don't subscribe yet
let expected = vec![AttServiceMessage::DiscoverPeers {
subnet_id: SubnetId::new(validator_index),
min_ttl,
}];
let subnet_id = SubnetId::compute_subnet::<MinimalEthSpec>(
current_slot + Slot::new(subscription_slot),
committee_index,
committee_count,
&attestation_service.beacon_chain.spec,
)
.unwrap();
let expected = vec![AttServiceMessage::DiscoverPeers { subnet_id, min_ttl }];
let events = get_events(attestation_service, no_events_expected, 1).await;
assert_matches!(
@ -325,6 +357,7 @@ mod tests {
let committee_index = 1;
let subscription_slot = 5;
let no_events_expected = 5;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -338,6 +371,7 @@ mod tests {
validator_index,
committee_index,
current_slot + Slot::new(subscription_slot),
committee_count,
)];
// submit the subscriptions
@ -354,12 +388,16 @@ mod tests {
);
// we should discover peers, wait, then subscribe
let subnet_id = SubnetId::compute_subnet::<MinimalEthSpec>(
current_slot + Slot::new(subscription_slot),
committee_index,
committee_count,
&attestation_service.beacon_chain.spec,
)
.unwrap();
let expected = vec![
AttServiceMessage::DiscoverPeers {
subnet_id: SubnetId::new(validator_index),
min_ttl,
},
AttServiceMessage::Subscribe(SubnetId::new(validator_index)),
AttServiceMessage::DiscoverPeers { subnet_id, min_ttl },
AttServiceMessage::Subscribe(subnet_id),
];
let events = get_events(attestation_service, no_events_expected, 5).await;
@ -387,6 +425,7 @@ mod tests {
let committee_index = 1;
let subscription_slot = 7;
let no_events_expected = 3;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -400,6 +439,7 @@ mod tests {
validator_index,
committee_index,
current_slot + Slot::new(subscription_slot),
committee_count,
)];
// submit the subscriptions
@ -436,9 +476,11 @@ mod tests {
let committee_index = 1;
let subscription_slot = 10;
let no_events_expected = 4;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
let current_slot = attestation_service
.beacon_chain
.slot_clock
@ -449,6 +491,7 @@ mod tests {
validator_index,
committee_index,
current_slot + Slot::new(subscription_slot),
committee_count,
)];
// submit the subscriptions
@ -464,11 +507,17 @@ mod tests {
.unwrap(),
);
let subnet_id = SubnetId::compute_subnet::<MinimalEthSpec>(
current_slot + Slot::new(subscription_slot),
committee_index,
committee_count,
&attestation_service.beacon_chain.spec,
)
.unwrap();
// expect discover peers because we will enter TARGET_PEER_DISCOVERY_SLOT_LOOK_AHEAD range
let expected: Vec<AttServiceMessage> = vec![AttServiceMessage::DiscoverPeers {
subnet_id: SubnetId::new(validator_index),
min_ttl,
}];
let expected: Vec<AttServiceMessage> =
vec![AttServiceMessage::DiscoverPeers { subnet_id, min_ttl }];
let events = get_events(attestation_service, no_events_expected, 5).await;
@ -494,6 +543,7 @@ mod tests {
// subscribe 10 slots ahead so we do not produce any exact subnet messages
let subscription_slot = 10;
let subscription_count = 64;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -503,8 +553,11 @@ mod tests {
.now()
.expect("Could not get current slot");
let subscriptions =
_get_subscriptions(subscription_count, current_slot + subscription_slot);
let subscriptions = _get_subscriptions(
subscription_count,
current_slot + subscription_slot,
committee_count,
);
// submit the subscriptions
attestation_service
@ -542,6 +595,7 @@ mod tests {
let subscription_slot = 10;
// the 65th subscription should result in no more messages than the previous scenario
let subscription_count = 65;
let committee_count = 1;
// create the attestation service and subscriptions
let mut attestation_service = get_attestation_service();
@ -551,8 +605,11 @@ mod tests {
.now()
.expect("Could not get current slot");
let subscriptions =
_get_subscriptions(subscription_count, current_slot + subscription_slot);
let subscriptions = _get_subscriptions(
subscription_count,
current_slot + subscription_slot,
committee_count,
);
// submit the subscriptions
attestation_service

View File

@ -234,6 +234,7 @@ impl<T: BeaconChainTypes> Router<T> {
self.processor.verify_unaggregated_attestation_for_gossip(
peer_id.clone(),
subnet_attestation.1.clone(),
subnet_attestation.0,
)
{
self.propagate_message(id, peer_id.clone());

View File

@ -17,7 +17,7 @@ use std::sync::Arc;
use tokio::sync::mpsc;
use types::{
Attestation, ChainSpec, Epoch, EthSpec, Hash256, SignedAggregateAndProof, SignedBeaconBlock,
Slot,
Slot, SubnetId,
};
//TODO: Rate limit requests
@ -758,6 +758,18 @@ impl<T: BeaconChainTypes> Processor<T> {
* The peer has published an invalid consensus message.
*/
}
AttnError::InvalidSubnetId { received, expected } => {
/*
* The attestation was received on an incorrect subnet id.
*/
debug!(
self.log,
"Received attestation on incorrect subnet";
"expected" => format!("{:?}", expected),
"received" => format!("{:?}", received),
)
}
AttnError::Invalid(_) => {
/*
* The attestation failed the state_processing verification.
@ -833,12 +845,13 @@ impl<T: BeaconChainTypes> Processor<T> {
&mut self,
peer_id: PeerId,
unaggregated_attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
) -> Option<VerifiedUnaggregatedAttestation<T>> {
// This is provided to the error handling function to assist with debugging.
let beacon_block_root = unaggregated_attestation.data.beacon_block_root;
self.chain
.verify_unaggregated_attestation_for_gossip(unaggregated_attestation)
.verify_unaggregated_attestation_for_gossip(unaggregated_attestation, subnet_id)
.map_err(|e| {
self.handle_attestation_verification_failure(
peer_id,

View File

@ -14,7 +14,7 @@ use eth2_libp2p::{
use eth2_libp2p::{BehaviourEvent, Enr, MessageId, NetworkGlobals, PeerId};
use futures::prelude::*;
use rest_types::ValidatorSubscription;
use slog::{debug, error, info, o, trace};
use slog::{debug, error, info, o, trace, warn};
use std::sync::Arc;
use std::time::Duration;
use store::HotColdDB;
@ -236,10 +236,11 @@ fn spawn_service<T: BeaconChainTypes>(
);
}
NetworkMessage::Subscribe { subscriptions } => {
// the result is dropped as it used solely for ergonomics
let _ = service
if let Err(e) = service
.attestation_service
.validator_subscriptions(subscriptions);
.validator_subscriptions(subscriptions) {
warn!(service.log, "Validator subscription failed"; "error" => e);
}
}
}
}
@ -327,8 +328,6 @@ fn spawn_service<T: BeaconChainTypes>(
// checks if we have an aggregator for the slot. If so, we process
// the attestation
if service.attestation_service.should_process_attestation(
&id,
&source,
subnet,
attestation,
) {

View File

@ -16,7 +16,7 @@ use std::sync::Arc;
use types::beacon_state::EthSpec;
use types::{
Attestation, AttestationData, BeaconState, Epoch, RelativeEpoch, SelectionProof,
SignedAggregateAndProof, SignedBeaconBlock, Slot,
SignedAggregateAndProof, SignedBeaconBlock, Slot, SubnetId,
};
/// HTTP Handler to retrieve the duties for a set of validators during a particular epoch. This
@ -220,6 +220,16 @@ fn return_validator_duties<T: BeaconChainTypes>(
))
})?;
let committee_count_at_slot = duties
.map(|d| state.get_committee_count_at_slot(d.slot))
.transpose()
.map_err(|e| {
ApiError::ServerError(format!(
"Unable to find committee count at slot: {:?}",
e
))
})?;
let aggregator_modulo = duties
.map(|duties| SelectionProof::modulo(duties.committee_len, &beacon_chain.spec))
.transpose()
@ -238,6 +248,7 @@ fn return_validator_duties<T: BeaconChainTypes>(
validator_index: Some(validator_index as u64),
attestation_slot: duties.map(|d| d.slot),
attestation_committee_index: duties.map(|d| d.index),
committee_count_at_slot,
attestation_committee_position: duties.map(|d| d.committee_position),
block_proposal_slots,
aggregator_modulo,
@ -249,6 +260,7 @@ fn return_validator_duties<T: BeaconChainTypes>(
attestation_slot: None,
attestation_committee_index: None,
attestation_committee_position: None,
committee_count_at_slot: None,
block_proposal_slots: vec![],
aggregator_modulo: None,
})
@ -443,21 +455,24 @@ pub async fn publish_attestations<T: BeaconChainTypes>(
))
})
// Process all of the aggregates _without_ exiting early if one fails.
.map(move |attestations: Vec<Attestation<T::EthSpec>>| {
attestations
.into_par_iter()
.enumerate()
.map(|(i, attestation)| {
process_unaggregated_attestation(
&beacon_chain,
network_chan.clone(),
attestation,
i,
&log,
)
})
.collect::<Vec<Result<_, _>>>()
})
.map(
move |attestations: Vec<(Attestation<T::EthSpec>, SubnetId)>| {
attestations
.into_par_iter()
.enumerate()
.map(|(i, (attestation, subnet_id))| {
process_unaggregated_attestation(
&beacon_chain,
network_chan.clone(),
attestation,
subnet_id,
i,
&log,
)
})
.collect::<Vec<Result<_, _>>>()
},
)
// Iterate through all the results and return on the first `Err`.
//
// Note: this will only provide info about the _first_ failure, not all failures.
@ -471,6 +486,7 @@ fn process_unaggregated_attestation<T: BeaconChainTypes>(
beacon_chain: &BeaconChain<T>,
network_chan: NetworkChannel<T::EthSpec>,
attestation: Attestation<T::EthSpec>,
subnet_id: SubnetId,
i: usize,
log: &Logger,
) -> Result<(), ApiError> {
@ -478,7 +494,7 @@ fn process_unaggregated_attestation<T: BeaconChainTypes>(
// Verify that the attestation is valid to included on the gossip network.
let verified_attestation = beacon_chain
.verify_unaggregated_attestation_for_gossip(attestation.clone())
.verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id)
.map_err(|e| {
handle_attestation_error(
e,
@ -491,9 +507,7 @@ fn process_unaggregated_attestation<T: BeaconChainTypes>(
// Publish the attestation to the network
if let Err(e) = network_chan.send(NetworkMessage::Publish {
messages: vec![PubsubMessage::Attestation(Box::new((
attestation
.subnet_id(&beacon_chain.spec)
.map_err(|e| ApiError::ServerError(format!("Unable to get subnet id: {:?}", e)))?,
subnet_id,
attestation,
)))],
}) {

View File

@ -22,7 +22,7 @@ use types::{
},
BeaconBlock, BeaconState, ChainSpec, Domain, Epoch, EthSpec, MinimalEthSpec, PublicKey,
RelativeEpoch, Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedRoot, Slot,
Validator,
SubnetId, Validator,
};
use version;
@ -144,7 +144,16 @@ fn validator_produce_attestation() {
))
.expect("should fetch duties from http api");
let duties = &duties[0];
let committee_count = duties
.committee_count_at_slot
.expect("should have committee count");
let subnet_id = SubnetId::compute_subnet::<E>(
attestation.data.slot,
attestation.data.index,
committee_count,
spec,
)
.unwrap();
// Try publishing the attestation without a signature or a committee bit set, ensure it is
// raises an error.
let publish_status = env
@ -153,7 +162,7 @@ fn validator_produce_attestation() {
remote_node
.http
.validator()
.publish_attestations(vec![attestation.clone()]),
.publish_attestations(vec![(attestation.clone(), subnet_id)]),
)
.expect("should publish unsigned attestation");
assert!(
@ -179,7 +188,7 @@ fn validator_produce_attestation() {
remote_node
.http
.validator()
.publish_attestations(vec![attestation.clone()]),
.publish_attestations(vec![(attestation.clone(), subnet_id)]),
)
.expect("should publish attestation with invalid signature");
assert!(
@ -217,7 +226,7 @@ fn validator_produce_attestation() {
remote_node
.http
.validator()
.publish_attestations(vec![attestation.clone()]),
.publish_attestations(vec![(attestation.clone(), subnet_id)]),
)
.expect("should publish attestation");
assert!(

View File

@ -12,7 +12,7 @@ use std::time::Duration;
use types::{
Attestation, AttestationData, AttesterSlashing, BeaconBlock, BeaconState, CommitteeIndex,
Epoch, EthSpec, Fork, Hash256, ProposerSlashing, PublicKey, PublicKeyBytes, Signature,
SignedAggregateAndProof, SignedBeaconBlock, Slot,
SignedAggregateAndProof, SignedBeaconBlock, Slot, SubnetId,
};
use url::Url;
@ -227,7 +227,7 @@ impl<E: EthSpec> Validator<E> {
/// Posts a list of attestations to the beacon node, expecting it to verify it and publish it to the network.
pub async fn publish_attestations(
&self,
attestation: Vec<Attestation<E>>,
attestation: Vec<(Attestation<E>, SubnetId)>,
) -> Result<PublishStatus, Error> {
let client = self.0.clone();
let url = self.url("attestations")?;

View File

@ -22,6 +22,8 @@ pub struct ValidatorDutyBase<T> {
pub attestation_committee_index: Option<CommitteeIndex>,
/// The position of the validator in the committee.
pub attestation_committee_position: Option<usize>,
/// The committee count at `attestation_slot`.
pub committee_count_at_slot: Option<u64>,
/// The slots in which a validator must propose a block (can be empty).
pub block_proposal_slots: Vec<Slot>,
/// This provides the modulo: `max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE)`
@ -66,6 +68,8 @@ pub struct ValidatorSubscription {
pub attestation_committee_index: CommitteeIndex,
/// The slot in which to subscribe.
pub slot: Slot,
/// Committee count at slot to subscribe.
pub committee_count_at_slot: u64,
/// If true, the validator is an aggregator and the beacon node should aggregate attestations
/// for this slot.
pub is_aggregator: bool,

View File

@ -12,7 +12,7 @@ use std::sync::Mutex;
use store::{MemoryStore, StoreConfig};
use types::{
test_utils::{generate_deterministic_keypair, generate_deterministic_keypairs},
Epoch, EthSpec, IndexedAttestation, MainnetEthSpec, Slot,
Epoch, EthSpec, IndexedAttestation, MainnetEthSpec, Slot, SubnetId,
};
use types::{BeaconBlock, BeaconState, Hash256, SignedBeaconBlock};
@ -285,6 +285,15 @@ impl ForkChoiceTest {
.get(validator_committee_index)
.expect("there should be an attesting validator");
let committee_count = head
.beacon_state
.get_committee_count_at_slot(current_slot)
.expect("should not error while getting committee count");
let subnet_id =
SubnetId::compute_subnet::<E>(current_slot, 0, committee_count, &chain.spec)
.expect("should compute subnet id");
let validator_sk = generate_deterministic_keypair(validator_index).sk;
attestation
@ -298,7 +307,7 @@ impl ForkChoiceTest {
.expect("should sign attestation");
let mut verified_attestation = chain
.verify_unaggregated_attestation_for_gossip(attestation)
.verify_unaggregated_attestation_for_gossip(attestation, subnet_id)
.expect("precondition: should gossip verify attestation");
if let MutationDelay::Blocks(slots) = delay {

View File

@ -1,10 +1,9 @@
use super::{
AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey,
Signature, SignedRoot, SubnetId,
Signature, SignedRoot,
};
use crate::{test_utils::TestRandom, Hash256};
use safe_arith::{ArithError, SafeArith};
use safe_arith::ArithError;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
@ -84,18 +83,6 @@ impl<T: EthSpec> Attestation<T> {
Ok(())
}
}
/// Returns the subnet id associated with the attestation.
///
/// Note, this will return the subnet id for an aggregated attestation. This is done
/// to avoid checking aggregate bits every time we wish to get an id.
pub fn subnet_id(&self, spec: &ChainSpec) -> Result<SubnetId, Error> {
self.data
.index
.safe_rem(spec.attestation_subnet_count)
.map(SubnetId::new)
.map_err(Error::SubnetCountIsZero)
}
}
#[cfg(test)]

View File

@ -83,12 +83,12 @@ impl<T: EthSpec> BeaconBlock<T> {
};
let proposer_slashing = ProposerSlashing {
signed_header_1: signed_header.clone(),
signed_header_2: signed_header.clone(),
signed_header_2: signed_header,
};
let attester_slashing = AttesterSlashing {
attestation_1: indexed_attestation.clone(),
attestation_2: indexed_attestation.clone(),
attestation_2: indexed_attestation,
};
let attestation: Attestation<T> = Attestation {

View File

@ -1,4 +1,6 @@
//! Identifies each shard by an integer identifier.
use crate::{AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot};
use safe_arith::{ArithError, SafeArith};
use serde_derive::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
@ -8,7 +10,42 @@ pub struct SubnetId(u64);
impl SubnetId {
pub fn new(id: u64) -> Self {
SubnetId(id)
id.into()
}
/// Compute the subnet for an attestation with `attestation_data` where each slot in the
/// attestation epoch contains `committee_count_per_slot` committees.
pub fn compute_subnet_for_attestation_data<T: EthSpec>(
attestation_data: &AttestationData,
committee_count_per_slot: u64,
spec: &ChainSpec,
) -> Result<SubnetId, ArithError> {
Self::compute_subnet::<T>(
attestation_data.slot,
attestation_data.index,
committee_count_per_slot,
spec,
)
}
/// Compute the subnet for an attestation with `attestation.data.slot == slot` and
/// `attestation.data.index == committee_index` where each slot in the attestation epoch
/// contains `committee_count_at_slot` committees.
pub fn compute_subnet<T: EthSpec>(
slot: Slot,
committee_index: CommitteeIndex,
committee_count_at_slot: u64,
spec: &ChainSpec,
) -> Result<SubnetId, ArithError> {
let slots_since_epoch_start: u64 = slot.as_u64().safe_rem(T::slots_per_epoch())?;
let committees_since_epoch_start =
committee_count_at_slot.safe_mul(slots_since_epoch_start)?;
Ok(committees_since_epoch_start
.safe_add(committee_index)?
.safe_rem(spec.attestation_subnet_count)?
.into())
}
}
@ -25,3 +62,15 @@ impl DerefMut for SubnetId {
&mut self.0
}
}
impl From<u64> for SubnetId {
fn from(x: u64) -> Self {
Self(x)
}
}
impl Into<u64> for SubnetId {
fn into(self) -> u64 {
self.0
}
}

View File

@ -5,13 +5,13 @@ use crate::{
use environment::RuntimeContext;
use futures::StreamExt;
use remote_beacon_node::{PublishStatus, RemoteBeaconNode};
use slog::{crit, debug, info, trace};
use slog::{crit, debug, error, info, trace};
use slot_clock::SlotClock;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use tokio::time::{delay_until, interval_at, Duration, Instant};
use types::{Attestation, ChainSpec, CommitteeIndex, EthSpec, Slot};
use types::{Attestation, ChainSpec, CommitteeIndex, EthSpec, Slot, SubnetId};
/// Builds an `AttestationService`.
pub struct AttestationServiceBuilder<T, E: EthSpec> {
@ -334,17 +334,22 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
.iter()
.filter_map(|duty| {
// Ensure that all required fields are present in the validator duty.
let (duty_slot, duty_committee_index, validator_committee_position, _) =
if let Some(tuple) = duty.attestation_duties() {
tuple
} else {
crit!(
log,
"Missing validator duties when signing";
"duties" => format!("{:?}", duty)
);
return None;
};
let (
duty_slot,
duty_committee_index,
validator_committee_position,
_,
committee_count_at_slot,
) = if let Some(tuple) = duty.attestation_duties() {
tuple
} else {
crit!(
log,
"Missing validator duties when signing";
"duties" => format!("{:?}", duty)
);
return None;
};
// Ensure that the attestation matches the duties.
if duty_slot != attestation.data.slot
@ -363,7 +368,18 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
}
let mut attestation = attestation.clone();
let subnet_id = SubnetId::compute_subnet_for_attestation_data::<E>(
&attestation.data,
committee_count_at_slot,
&self.context.eth2_config().spec,
)
.map_err(|e| {
error!(
log,
"Failed to compute subnet id to publish attestation: {:?}", e
)
})
.ok()?;
self.validator_store
.sign_attestation(
duty.validator_pubkey(),
@ -371,7 +387,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
&mut attestation,
current_epoch,
)
.map(|_| attestation)
.map(|_| (attestation, subnet_id))
})
.collect::<Vec<_>>();
@ -379,7 +395,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
// just return early.
if let Some(attestation) = signed_attestations.first().cloned() {
let num_attestations = signed_attestations.len();
let beacon_block_root = attestation.data.beacon_block_root;
let beacon_block_root = attestation.0.data.beacon_block_root;
self.beacon_node
.http
@ -409,7 +425,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
crit!(log, "Unknown condition when publishing unagg. attestation")
}
})
.map(|()| Some(attestation))
.map(|()| Some(attestation.0))
} else {
debug!(
log,
@ -459,7 +475,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
// subscribed aggregators.
let selection_proof = duty_and_proof.selection_proof.as_ref()?.clone();
let (duty_slot, duty_committee_index, _, validator_index) =
let (duty_slot, duty_committee_index, _, validator_index, _) =
duty_and_proof.attestation_duties().or_else(|| {
crit!(log, "Missing duties when signing aggregate");
None

View File

@ -90,12 +90,13 @@ impl DutyAndProof {
/// Returns the information required for an attesting validator, if they are scheduled to
/// attest.
pub fn attestation_duties(&self) -> Option<(Slot, CommitteeIndex, usize, u64)> {
pub fn attestation_duties(&self) -> Option<(Slot, CommitteeIndex, usize, u64, u64)> {
Some((
self.duty.attestation_slot?,
self.duty.attestation_committee_index?,
self.duty.attestation_committee_position?,
self.duty.validator_index?,
self.duty.committee_count_at_slot?,
))
}
@ -116,6 +117,7 @@ impl TryInto<DutyAndProof> for ValidatorDutyBytes {
attestation_slot: self.attestation_slot,
attestation_committee_index: self.attestation_committee_index,
attestation_committee_position: self.attestation_committee_position,
committee_count_at_slot: self.committee_count_at_slot,
block_proposal_slots: self.block_proposal_slots,
aggregator_modulo: self.aggregator_modulo,
};
@ -609,6 +611,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
validator_index: remote_duties.validator_index?,
attestation_committee_index: remote_duties.attestation_committee_index?,
slot: remote_duties.attestation_slot?,
committee_count_at_slot: remote_duties.committee_count_at_slot?,
is_aggregator,
})
} else {