some tests and fixed attestqtion calc

This commit is contained in:
Eitan Seri-Levi
2024-12-20 22:53:04 +07:00
parent 5a4cd3bb2c
commit f56083de65
7 changed files with 332 additions and 37 deletions

View File

@@ -16,6 +16,7 @@ use crate::{
BeaconChain, BeaconChainTypes, BlockError, ChainConfig, ServerSentEventHandler, BeaconChain, BeaconChainTypes, BlockError, ChainConfig, ServerSentEventHandler,
StateSkipConfig, StateSkipConfig,
}; };
use attestation::SingleAttestation;
use bls::get_withdrawal_credentials; use bls::get_withdrawal_credentials;
use eth2::types::SignedBlockContentsTuple; use eth2::types::SignedBlockContentsTuple;
use execution_layer::test_utils::generate_genesis_header; use execution_layer::test_utils::generate_genesis_header;
@@ -662,10 +663,14 @@ pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub rng: Mutex<StdRng>, pub rng: Mutex<StdRng>,
} }
pub type CommitteeSingleAttestations = Vec<(SingleAttestation, SubnetId)>;
pub type CommitteeAttestations<E> = Vec<(Attestation<E>, SubnetId)>; pub type CommitteeAttestations<E> = Vec<(Attestation<E>, SubnetId)>;
pub type HarnessAttestations<E> = pub type HarnessAttestations<E> =
Vec<(CommitteeAttestations<E>, Option<SignedAggregateAndProof<E>>)>; Vec<(CommitteeAttestations<E>, Option<SignedAggregateAndProof<E>>)>;
pub type HarnessSingleAttestations<E> =
Vec<(CommitteeSingleAttestations, Option<SignedAggregateAndProof<E>>)>;
pub type HarnessSyncContributions<E> = Vec<( pub type HarnessSyncContributions<E> = Vec<(
Vec<(SyncCommitteeMessage, usize)>, Vec<(SyncCommitteeMessage, usize)>,
Option<SignedContributionAndProof<E>>, Option<SignedContributionAndProof<E>>,
@@ -1023,6 +1028,79 @@ where
) )
} }
pub fn produce_single_attestation_for_block(
&self,
slot: Slot,
index: CommitteeIndex,
beacon_block_root: Hash256,
mut state: Cow<BeaconState<E>>,
state_root: Hash256,
aggregation_bit_index: usize,
validator_index: usize,
) -> Result<SingleAttestation, BeaconChainError> {
let epoch = slot.epoch(E::slots_per_epoch());
if state.slot() > slot {
return Err(BeaconChainError::CannotAttestToFutureState);
} else if state.current_epoch() < epoch {
let mut_state = state.to_mut();
complete_state_advance(
mut_state,
Some(state_root),
epoch.start_slot(E::slots_per_epoch()),
&self.spec,
)?;
mut_state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
}
let committee_len = state.get_beacon_committee(slot, index)?.committee.len();
let target_slot = epoch.start_slot(E::slots_per_epoch());
let target_root = if state.slot() <= target_slot {
beacon_block_root
} else {
*state.get_block_root(target_slot)?
};
let mut attestation: Attestation<E> = Attestation::empty_for_signing(
index,
committee_len,
slot,
beacon_block_root,
state.current_justified_checkpoint(),
Checkpoint {
epoch,
root: target_root,
},
&self.spec,
)?;
let attestation = match attestation {
Attestation::Electra(mut attn) => {
attn.aggregation_bits.set(aggregation_bit_index, true).unwrap();
attn
},
Attestation::Base(_) => panic!("MUST BE AN ELECTRA ATTESTATION"),
};
let committee = state.get_beacon_committee(slot, index)?;
// let committees = state.get_beacon_committees_at_epoch(RelativeEpoch::Current)?;
let single_attestation = attestation.to_single_attestation(
&vec![committee.clone()]
)?;
let attestation: Attestation<E> = single_attestation.to_attestation(&vec![committee])?;
assert_eq!(single_attestation.committee_index, attestation.committee_index().unwrap() as usize);
assert_eq!(single_attestation.attester_index, validator_index);
// assert_eq!(single_attestation.attester_index, attestation.attester_index());
Ok(single_attestation)
}
/// Produces an "unaggregated" attestation for the given `slot` and `index` that attests to /// Produces an "unaggregated" attestation for the given `slot` and `index` that attests to
/// `beacon_block_root`. The provided `state` should match the `block.state_root` for the /// `beacon_block_root`. The provided `state` should match the `block.state_root` for the
/// `block` identified by `beacon_block_root`. /// `block` identified by `beacon_block_root`.
@@ -1080,6 +1158,33 @@ where
)?) )?)
} }
/// A list of attestations for each committee for the given slot.
///
/// The first layer of the Vec is organised per committee. For example, if the return value is
/// called `all_attestations`, then all attestations in `all_attestations[0]` will be for
/// committee 0, whilst all in `all_attestations[1]` will be for committee 1.
pub fn make_single_attestations(
&self,
attesting_validators: &[usize],
state: &BeaconState<E>,
state_root: Hash256,
head_block_root: SignedBeaconBlockHash,
attestation_slot: Slot,
) -> Vec<CommitteeSingleAttestations> {
let fork = self
.spec
.fork_at_epoch(attestation_slot.epoch(E::slots_per_epoch()));
self.make_single_attestations_with_opts(
attesting_validators,
state,
state_root,
head_block_root,
attestation_slot,
MakeAttestationOptions { limit: None, fork },
)
.0
}
/// A list of attestations for each committee for the given slot. /// A list of attestations for each committee for the given slot.
/// ///
/// The first layer of the Vec is organised per committee. For example, if the return value is /// The first layer of the Vec is organised per committee. For example, if the return value is
@@ -1107,6 +1212,99 @@ where
.0 .0
} }
pub fn make_single_attestations_with_opts(
&self,
attesting_validators: &[usize],
state: &BeaconState<E>,
state_root: Hash256,
head_block_root: SignedBeaconBlockHash,
attestation_slot: Slot,
opts: MakeAttestationOptions,
) -> (Vec<CommitteeSingleAttestations>, Vec<usize>) {
let MakeAttestationOptions { limit, fork } = opts;
let committee_count = state.get_committee_count_at_slot(state.slot()).unwrap();
let num_attesters = AtomicUsize::new(0);
let (attestations, split_attesters) = state
.get_beacon_committees_at_slot(attestation_slot)
.expect("should get committees")
.iter()
.map(|bc| {
bc.committee
.par_iter()
.enumerate()
.filter_map(|(i, validator_index)| {
if !attesting_validators.contains(validator_index) {
return None;
}
if let Some(limit) = limit {
// This atomics stuff is necessary because we're under a par_iter,
// and Rayon will deadlock if we use a mutex.
if num_attesters.fetch_add(1, Ordering::Relaxed) >= limit {
num_attesters.fetch_sub(1, Ordering::Relaxed);
return None;
}
}
let mut attestation = self
.produce_single_attestation_for_block(
attestation_slot,
bc.index,
head_block_root.into(),
Cow::Borrowed(state),
state_root,
i,
*validator_index
)
.unwrap();
attestation.signature = {
let domain = self.spec.get_domain(
attestation.data.target.epoch,
Domain::BeaconAttester,
&fork,
state.genesis_validators_root(),
);
let message = attestation.data.signing_root(domain);
let mut agg_sig = AggregateSignature::infinity();
agg_sig.add_assign(
&self.validator_keypairs[*validator_index].sk.sign(message),
);
agg_sig
};
let subnet_id = SubnetId::compute_subnet_for_single_attestation::<E>(
attestation.clone(),
committee_count,
&self.chain.spec,
)
.unwrap();
Some(((attestation, subnet_id), validator_index))
})
.unzip::<_, _, Vec<_>, Vec<_>>()
})
.unzip::<_, _, Vec<_>, Vec<_>>();
// Flatten attesters.
let attesters = split_attesters.into_iter().flatten().collect::<Vec<_>>();
if let Some(limit) = limit {
assert_eq!(limit, num_attesters.load(Ordering::Relaxed));
assert_eq!(
limit,
attesters.len(),
"failed to generate `limit` attestations"
);
}
(attestations, attesters)
}
pub fn make_unaggregated_attestations_with_opts( pub fn make_unaggregated_attestations_with_opts(
&self, &self,
attesting_validators: &[usize], attesting_validators: &[usize],
@@ -1287,6 +1485,32 @@ where
) )
} }
/// A list of attestations for each committee for the given slot.
///
/// The first layer of the Vec is organised per committee. For example, if the return value is
/// called `all_attestations`, then all attestations in `all_attestations[0]` will be for
/// committee 0, whilst all in `all_attestations[1]` will be for committee 1.
pub fn get_single_attestations(
&self,
attestation_strategy: &AttestationStrategy,
state: &BeaconState<E>,
state_root: Hash256,
head_block_root: Hash256,
attestation_slot: Slot,
) -> Vec<Vec<(SingleAttestation, SubnetId)>> {
let validators: Vec<usize> = match attestation_strategy {
AttestationStrategy::AllValidators => self.get_all_validators(),
AttestationStrategy::SomeValidators(vals) => vals.clone(),
};
self.make_single_attestations(
&validators,
state,
state_root,
head_block_root.into(),
attestation_slot,
)
}
pub fn make_attestations( pub fn make_attestations(
&self, &self,
attesting_validators: &[usize], attesting_validators: &[usize],

View File

@@ -155,10 +155,10 @@ async fn attestations_across_fork_with_skip_slots() {
.post_beacon_pool_attestations_v1(&unaggregated_attestations) .post_beacon_pool_attestations_v1(&unaggregated_attestations)
.await .await
.unwrap(); .unwrap();
client // client
.post_beacon_pool_attestations_v2(&unaggregated_attestations, fork_name) // .post_beacon_pool_attestations_v2(&unaggregated_attestations, fork_name)
.await // .await
.unwrap(); // .unwrap();
let signed_aggregates = attestations let signed_aggregates = attestations
.into_iter() .into_iter()

View File

@@ -853,6 +853,53 @@ pub async fn fork_choice_before_proposal() {
assert_eq!(block_d.parent_root(), Hash256::from(block_root_b)); assert_eq!(block_d.parent_root(), Hash256::from(block_root_b));
} }
// Test that attestations to unknown blocks are requeued and processed when their block arrives.
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn valid_single_attestation() {
let validator_count = 128;
let all_validators = (0..validator_count).collect::<Vec<_>>();
let tester = InteractiveTester::<E>::new(None, validator_count).await;
let harness = &tester.harness;
let client = tester.client.clone();
let num_initial = 5;
// Slot of the block attested to.
let attestation_slot = Slot::new(num_initial) + 1;
// Make some initial blocks.
harness.advance_slot();
harness
.extend_chain(
num_initial as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
)
.await;
harness.advance_slot();
assert_eq!(harness.get_current_slot(), attestation_slot);
// Make the attested-to block without applying it.
let pre_state = harness.get_current_state();
let (block, post_state) = harness.make_block(pre_state, attestation_slot).await;
let block_root = block.0.canonical_root();
// Make attestations to the block and POST them to the beacon node on a background thread.
let attestations = harness
.make_single_attestations(
&all_validators,
&post_state,
block.0.state_root(),
block_root.into(),
attestation_slot,
)
.into_iter()
.flat_map(|attestations| attestations.into_iter().map(|(att, _subnet)| att))
.collect::<Vec<_>>();
}
// Test that attestations to unknown blocks are requeued and processed when their block arrives. // Test that attestations to unknown blocks are requeued and processed when their block arrives.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)] #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn queue_attestations_from_http() { async fn queue_attestations_from_http() {
@@ -902,7 +949,7 @@ async fn queue_attestations_from_http() {
let fork_name = tester.harness.spec.fork_name_at_slot::<E>(attestation_slot); let fork_name = tester.harness.spec.fork_name_at_slot::<E>(attestation_slot);
let attestation_future = tokio::spawn(async move { let attestation_future = tokio::spawn(async move {
client client
.post_beacon_pool_attestations_v2(&attestations, fork_name) .post_beacon_pool_attestations_v1(&attestations)
.await .await
.expect("attestations should be processed successfully") .expect("attestations should be processed successfully")
}); });

View File

@@ -1810,7 +1810,7 @@ impl ApiTester {
.map(|att| self.chain.spec.fork_name_at_slot::<E>(att.data().slot)) .map(|att| self.chain.spec.fork_name_at_slot::<E>(att.data().slot))
.unwrap(); .unwrap();
self.client self.client
.post_beacon_pool_attestations_v2(self.attestations.as_slice(), fork_name) .post_beacon_pool_attestations_v1(self.attestations.as_slice())
.await .await
.unwrap(); .unwrap();
assert!( assert!(
@@ -1876,7 +1876,7 @@ impl ApiTester {
let err_v2 = self let err_v2 = self
.client .client
.post_beacon_pool_attestations_v2(attestations.as_slice(), fork_name) .post_beacon_pool_attestations_v1(attestations.as_slice())
.await .await
.unwrap_err(); .unwrap_err();

View File

@@ -413,28 +413,32 @@ impl<E: EthSpec> AttestationElectra<E> {
.first() .first()
.ok_or(Error::InvalidAggregationBit)?; .ok_or(Error::InvalidAggregationBit)?;
println!("committees! {:?}", committees);
let beacon_committee = committees let beacon_committee = committees
.get(committee_index as usize) .get(committee_index as usize)
.ok_or(Error::InvalidCommitteeIndex)?; .ok_or(Error::InvalidCommitteeIndex)?;
let attester_indices = beacon_committee println!("agg bit {} committee index {}", aggregation_bit, committee_index);
println!("agg bit {}", aggregation_bit);
println!("agg bit {} committees {:?}", aggregation_bit, beacon_committee.committee);
let attester_index = beacon_committee
.committee .committee
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(i, &index)| { .find_map(|(i, &index)| {
println!("agg bit {} val index {}", aggregation_bit, index);
println!("agg bit {} ith {}", aggregation_bit, i);
if aggregation_bit as usize == i { if aggregation_bit as usize == i {
println!("agg bit {} RETURNED INDEX {}", aggregation_bit, index);
return Some(index); return Some(index);
} }
None None
}) });
.collect::<Vec<_>>();
if attester_indices.len() != 1 { let attester_index = attester_index
return Err(Error::InvalidAggregationBit);
};
let attester_index = *attester_indices
.first()
.ok_or(Error::InvalidAggregationBit)?; .ok_or(Error::InvalidAggregationBit)?;
Ok(SingleAttestation { Ok(SingleAttestation {
@@ -674,36 +678,39 @@ impl SingleAttestation {
&self, &self,
committees: &[BeaconCommittee], committees: &[BeaconCommittee],
) -> Result<Attestation<E>, Error> { ) -> Result<Attestation<E>, Error> {
let beacon_committee = committees
.get(self.committee_index)
.ok_or(Error::InvalidAggregationBit)?;
let aggregation_bits = beacon_committee
.committee
.iter()
.enumerate()
.filter_map(|(i, &validator_index)| {
if self.attester_index == validator_index {
return Some(i);
}
None
})
.collect::<Vec<_>>();
if aggregation_bits.len() != 1 { let mut committee_offset = 0;
return Err(Error::InvalidAggregationBit); let mut aggregation_bit: Option<usize> = None;
for beacon_committee in committees {
if beacon_committee.index == self.committee_index as u64 {
aggregation_bit = beacon_committee
.committee
.iter()
.enumerate()
.find_map(|(i, &validator_index)| {
if self.attester_index == validator_index {
return Some(i + committee_offset);
}
None
});
committee_offset += beacon_committee.committee.len();
break;
} else {
committee_offset += beacon_committee.committee.len();
}
} }
let aggregation_bit = aggregation_bits.first().unwrap(); let aggregation_bit = aggregation_bit.ok_or(Error::InvalidAggregationBit)?;
let mut committee_bits: BitVector<E::MaxCommitteesPerSlot> = BitVector::default(); let mut committee_bits: BitVector<E::MaxCommitteesPerSlot> = BitVector::default();
committee_bits committee_bits
.set(self.committee_index, true) .set(self.committee_index, true)
.map_err(|_| Error::InvalidCommitteeIndex)?; .map_err(|_| Error::InvalidCommitteeIndex)?;
let mut aggregation_bits = BitList::with_capacity(beacon_committee.committee.len()) let mut aggregation_bits = BitList::with_capacity(committee_offset)
.map_err(|_| Error::InvalidCommitteeLength)?; .map_err(|_| Error::InvalidCommitteeLength)?;
aggregation_bits.set(*aggregation_bit, true)?; aggregation_bits.set(aggregation_bit, true)?;
Ok(Attestation::Electra(AttestationElectra { Ok(Attestation::Electra(AttestationElectra {
aggregation_bits, aggregation_bits,

View File

@@ -1,4 +1,5 @@
//! Identifies each shard by an integer identifier. //! Identifies each shard by an integer identifier.
use crate::attestation::SingleAttestation;
use crate::{AttestationRef, ChainSpec, CommitteeIndex, EthSpec, Slot}; use crate::{AttestationRef, ChainSpec, CommitteeIndex, EthSpec, Slot};
use alloy_primitives::{bytes::Buf, U256}; use alloy_primitives::{bytes::Buf, U256};
use safe_arith::{ArithError, SafeArith}; use safe_arith::{ArithError, SafeArith};
@@ -57,6 +58,21 @@ impl SubnetId {
) )
} }
/// Compute the subnet for an attestation where each slot in the
/// attestation epoch contains `committee_count_per_slot` committees.
pub fn compute_subnet_for_single_attestation<E: EthSpec>(
attestation: SingleAttestation,
committee_count_per_slot: u64,
spec: &ChainSpec,
) -> Result<SubnetId, ArithError> {
Self::compute_subnet::<E>(
attestation.data.slot,
attestation.committee_index as u64,
committee_count_per_slot,
spec,
)
}
/// Compute the subnet for an attestation with `attestation.data.slot == slot` and /// Compute the subnet for an attestation with `attestation.data.slot == slot` and
/// `attestation.data.index == committee_index` where each slot in the attestation epoch /// `attestation.data.index == committee_index` where each slot in the attestation epoch
/// contains `committee_count_at_slot` committees. /// contains `committee_count_at_slot` committees.

View File

@@ -31,6 +31,7 @@ fn signed_att(attestation: &AttestationData) -> SignedAttestation {
SignedAttestation::from_attestation(attestation, DEFAULT_DOMAIN) SignedAttestation::from_attestation(attestation, DEFAULT_DOMAIN)
} }
#[test] #[test]
fn valid_empty_history() { fn valid_empty_history() {
StreamTest { StreamTest {