02e2fd2fb8
## Issue Addressed NA ## Proposed Changes Introduces a cache to attestation to produce atop blocks which will become the head, but are not fully imported (e.g., not inserted into the database). Whilst attesting to a block before it's imported is rather easy, if we're going to produce that attestation then we also need to be able to: 1. Verify that attestation. 1. Respond to RPC requests for the `beacon_block_root`. Attestation verification (1) is *partially* covered. Since we prime the shuffling cache before we insert the block into the early attester cache, we should be fine for all typical use-cases. However, it is possible that the cache is washed out before we've managed to insert the state into the database and then attestation verification will fail with a "missing beacon state"-type error. Providing the block via RPC (2) is also partially covered, since we'll check the database *and* the early attester cache when responding a blocks-by-root request. However, we'll still omit the block from blocks-by-range requests (until the block lands in the DB). I *think* this is fine, since there's no guarantee that we return all blocks for those responses. Another important consideration is whether or not the *parent* of the early attester block is available in the databse. If it were not, we might fail to respond to blocks-by-root request that are iterating backwards to collect a chain of blocks. I argue that *we will always have the parent of the early attester block in the database.* This is because we are holding the fork-choice write-lock when inserting the block into the early attester cache and we do not drop that until the block is in the database.
146 lines
5.0 KiB
Rust
146 lines
5.0 KiB
Rust
#![cfg(not(debug_assertions))]
|
|
|
|
use beacon_chain::test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy};
|
|
use beacon_chain::{StateSkipConfig, WhenSlotSkipped};
|
|
use lazy_static::lazy_static;
|
|
use tree_hash::TreeHash;
|
|
use types::{AggregateSignature, EthSpec, Keypair, MainnetEthSpec, RelativeEpoch, Slot};
|
|
|
|
pub const VALIDATOR_COUNT: usize = 16;
|
|
|
|
lazy_static! {
|
|
/// A cached set of keys.
|
|
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
|
|
}
|
|
|
|
/// This test builds a chain that is just long enough to finalize an epoch then it produces an
|
|
/// attestation at each slot from genesis through to three epochs past the head.
|
|
///
|
|
/// It checks the produced attestation against some locally computed values.
|
|
#[test]
|
|
fn produces_attestations() {
|
|
let num_blocks_produced = MainnetEthSpec::slots_per_epoch() * 4;
|
|
let additional_slots_tested = MainnetEthSpec::slots_per_epoch() * 3;
|
|
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
.default_spec()
|
|
.keypairs(KEYPAIRS[..].to_vec())
|
|
.fresh_ephemeral_store()
|
|
.build();
|
|
|
|
let chain = &harness.chain;
|
|
|
|
// Test all valid committee indices for all slots in the chain.
|
|
// for slot in 0..=current_slot.as_u64() + MainnetEthSpec::slots_per_epoch() * 3 {
|
|
for slot in 0..=num_blocks_produced + additional_slots_tested {
|
|
if slot > 0 && slot <= num_blocks_produced {
|
|
harness.advance_slot();
|
|
|
|
harness.extend_chain(
|
|
1,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
);
|
|
}
|
|
|
|
let slot = Slot::from(slot);
|
|
let mut state = chain
|
|
.state_at_slot(slot, StateSkipConfig::WithStateRoots)
|
|
.expect("should get state");
|
|
|
|
let block_slot = if slot <= num_blocks_produced {
|
|
slot
|
|
} else {
|
|
Slot::from(num_blocks_produced)
|
|
};
|
|
|
|
let block = chain
|
|
.block_at_slot(block_slot, WhenSlotSkipped::Prev)
|
|
.expect("should get block")
|
|
.expect("block should not be skipped");
|
|
let block_root = block.message().tree_hash_root();
|
|
|
|
let epoch_boundary_slot = state
|
|
.current_epoch()
|
|
.start_slot(MainnetEthSpec::slots_per_epoch());
|
|
let target_root = if state.slot() == epoch_boundary_slot {
|
|
block_root
|
|
} else {
|
|
*state
|
|
.get_block_root(epoch_boundary_slot)
|
|
.expect("should get target block root")
|
|
};
|
|
|
|
state
|
|
.build_committee_cache(RelativeEpoch::Current, &harness.chain.spec)
|
|
.unwrap();
|
|
let committee_cache = state
|
|
.committee_cache(RelativeEpoch::Current)
|
|
.expect("should get committee_cache");
|
|
|
|
let committee_count = committee_cache.committees_per_slot();
|
|
|
|
for index in 0..committee_count {
|
|
let committee_len = committee_cache
|
|
.get_beacon_committee(slot, index)
|
|
.expect("should get committee for slot")
|
|
.committee
|
|
.len();
|
|
|
|
let attestation = chain
|
|
.produce_unaggregated_attestation(slot, index)
|
|
.expect("should produce attestation");
|
|
|
|
let data = &attestation.data;
|
|
|
|
assert_eq!(
|
|
attestation.aggregation_bits.len(),
|
|
committee_len,
|
|
"bad committee len"
|
|
);
|
|
assert!(
|
|
attestation.aggregation_bits.is_zero(),
|
|
"some committee bits are set"
|
|
);
|
|
assert_eq!(
|
|
attestation.signature,
|
|
AggregateSignature::empty(),
|
|
"bad signature"
|
|
);
|
|
assert_eq!(data.index, index, "bad index");
|
|
assert_eq!(data.slot, slot, "bad slot");
|
|
assert_eq!(data.beacon_block_root, block_root, "bad block root");
|
|
assert_eq!(
|
|
data.source,
|
|
state.current_justified_checkpoint(),
|
|
"bad source"
|
|
);
|
|
assert_eq!(
|
|
data.source,
|
|
state.current_justified_checkpoint(),
|
|
"bad source"
|
|
);
|
|
assert_eq!(data.target.epoch, state.current_epoch(), "bad target epoch");
|
|
assert_eq!(data.target.root, target_root, "bad target root");
|
|
|
|
let early_attestation = {
|
|
let proto_block = chain.fork_choice.read().get_block(&block_root).unwrap();
|
|
chain
|
|
.early_attester_cache
|
|
.add_head_block(block_root, block.clone(), proto_block, &state, &chain.spec)
|
|
.unwrap();
|
|
chain
|
|
.early_attester_cache
|
|
.try_attest(slot, index, &chain.spec)
|
|
.unwrap()
|
|
.unwrap()
|
|
};
|
|
|
|
assert_eq!(
|
|
attestation, early_attestation,
|
|
"early attester cache inconsistent"
|
|
);
|
|
}
|
|
}
|
|
}
|