mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-03 12:54:27 +00:00
Delete inaccurate block replay
This commit is contained in:
@@ -3919,6 +3919,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
"head_block_root" => head_block_root.to_string(),
|
"head_block_root" => head_block_root.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If the block's state will be so far ahead of `shuffling_epoch` that even its
|
||||||
|
// previous epoch committee cache will be too new, then error. Callers of this function
|
||||||
|
// shouldn't be requesting such old shufflings for this `head_block_root`.
|
||||||
|
let head_block_epoch = head_block.slot.epoch(T::EthSpec::slots_per_epoch());
|
||||||
|
if head_block_epoch > shuffling_epoch + 1 {
|
||||||
|
return Err(Error::InvalidStateForShuffling {
|
||||||
|
state_epoch: head_block_epoch,
|
||||||
|
shuffling_epoch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let state_read_timer =
|
let state_read_timer =
|
||||||
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_READ_TIMES);
|
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_READ_TIMES);
|
||||||
|
|
||||||
@@ -3939,59 +3950,46 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Compute the `target_slot` to advance the block's state to.
|
||||||
|
//
|
||||||
|
// Since there's a one-epoch look-ahead on the attester shuffling, it suffices to
|
||||||
|
// only advance into the first slot of the epoch prior to `shuffling_epoch`.
|
||||||
|
//
|
||||||
|
// If the `head_block` is already ahead of that slot, then we should load the state
|
||||||
|
// at that slot, as we've determined above that the `shuffling_epoch` cache will
|
||||||
|
// not be too far in the past.
|
||||||
|
let target_slot = std::cmp::max(
|
||||||
|
shuffling_epoch
|
||||||
|
.saturating_sub(1_u64)
|
||||||
|
.start_slot(T::EthSpec::slots_per_epoch()),
|
||||||
|
head_block.slot,
|
||||||
|
);
|
||||||
|
|
||||||
// If the head state is useful for this request, use it. Otherwise, read a state from
|
// If the head state is useful for this request, use it. Otherwise, read a state from
|
||||||
// disk.
|
// disk that is advanced as close as possible to `target_slot`.
|
||||||
let (mut state, state_root) = if let Some((state, state_root)) = head_state_opt {
|
let (mut state, state_root) = if let Some((state, state_root)) = head_state_opt {
|
||||||
(state, state_root)
|
(state, state_root)
|
||||||
} else {
|
} else {
|
||||||
let state_root = head_block.state_root;
|
let (state_root, state) = self
|
||||||
let state = self
|
|
||||||
.store
|
.store
|
||||||
.get_inconsistent_state_for_attestation_verification_only(
|
.get_advanced_state(head_block_root, target_slot, head_block.state_root)?
|
||||||
&state_root,
|
|
||||||
Some(head_block.slot),
|
|
||||||
)?
|
|
||||||
.ok_or(Error::MissingBeaconState(head_block.state_root))?;
|
.ok_or(Error::MissingBeaconState(head_block.state_root))?;
|
||||||
(state, state_root)
|
(state, state_root)
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* IMPORTANT
|
|
||||||
*
|
|
||||||
* Since it's possible that
|
|
||||||
* `Store::get_inconsistent_state_for_attestation_verification_only` was used to obtain
|
|
||||||
* the state, we cannot rely upon the following fields:
|
|
||||||
*
|
|
||||||
* - `state.state_roots`
|
|
||||||
* - `state.block_roots`
|
|
||||||
*
|
|
||||||
* These fields should not be used for the rest of this function.
|
|
||||||
*/
|
|
||||||
|
|
||||||
metrics::stop_timer(state_read_timer);
|
metrics::stop_timer(state_read_timer);
|
||||||
let state_skip_timer =
|
let state_skip_timer =
|
||||||
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_SKIP_TIMES);
|
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_SKIP_TIMES);
|
||||||
|
|
||||||
// If the state is in an earlier epoch, advance it. If it's from a later epoch, reject
|
// If the state is still in an earlier epoch, advance it to the `target_slot` so
|
||||||
// it.
|
// that its next epoch committee cache matches the `shuffling_epoch`.
|
||||||
if state.current_epoch() + 1 < shuffling_epoch {
|
if state.current_epoch() + 1 < shuffling_epoch {
|
||||||
// Since there's a one-epoch look-ahead on the attester shuffling, it suffices to
|
// Advance the state into the required slot, using the "partial" method since the
|
||||||
// only advance into the slot prior to the `shuffling_epoch`.
|
// state roots are not relevant for the shuffling.
|
||||||
let target_slot = shuffling_epoch
|
|
||||||
.saturating_sub(1_u64)
|
|
||||||
.start_slot(T::EthSpec::slots_per_epoch());
|
|
||||||
|
|
||||||
// Advance the state into the required slot, using the "partial" method since the state
|
|
||||||
// roots are not relevant for the shuffling.
|
|
||||||
partial_state_advance(&mut state, Some(state_root), target_slot, &self.spec)?;
|
partial_state_advance(&mut state, Some(state_root), target_slot, &self.spec)?;
|
||||||
} else if state.current_epoch() > shuffling_epoch {
|
|
||||||
return Err(Error::InvalidStateForShuffling {
|
|
||||||
state_epoch: state.current_epoch(),
|
|
||||||
shuffling_epoch,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics::stop_timer(state_skip_timer);
|
metrics::stop_timer(state_skip_timer);
|
||||||
|
|
||||||
let committee_building_timer =
|
let committee_building_timer =
|
||||||
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_COMMITTEE_BUILDING_TIMES);
|
metrics::start_timer(&metrics::ATTESTATION_PROCESSING_COMMITTEE_BUILDING_TIMES);
|
||||||
|
|
||||||
|
|||||||
@@ -412,48 +412,6 @@ fn forwards_iter_block_and_state_roots_until() {
|
|||||||
test_range(Slot::new(0), head_state.slot());
|
test_range(Slot::new(0), head_state.slot());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_replay_with_inaccurate_state_roots() {
|
|
||||||
let num_blocks_produced = E::slots_per_epoch() * 3 + 31;
|
|
||||||
let db_path = tempdir().unwrap();
|
|
||||||
let store = get_store(&db_path);
|
|
||||||
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
|
|
||||||
let chain = &harness.chain;
|
|
||||||
|
|
||||||
harness.extend_chain(
|
|
||||||
num_blocks_produced as usize,
|
|
||||||
BlockStrategy::OnCanonicalHead,
|
|
||||||
AttestationStrategy::AllValidators,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Slot must not be 0 mod 32 or else no blocks will be replayed.
|
|
||||||
let (mut head_state, head_root) = harness.get_current_state_and_root();
|
|
||||||
assert_ne!(head_state.slot() % 32, 0);
|
|
||||||
|
|
||||||
let mut fast_head_state = store
|
|
||||||
.get_inconsistent_state_for_attestation_verification_only(
|
|
||||||
&head_root,
|
|
||||||
Some(head_state.slot()),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(head_state.validators(), fast_head_state.validators());
|
|
||||||
|
|
||||||
head_state.build_all_committee_caches(&chain.spec).unwrap();
|
|
||||||
fast_head_state
|
|
||||||
.build_all_committee_caches(&chain.spec)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
head_state
|
|
||||||
.get_cached_active_validator_indices(RelativeEpoch::Current)
|
|
||||||
.unwrap(),
|
|
||||||
fast_head_state
|
|
||||||
.get_cached_active_validator_indices(RelativeEpoch::Current)
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_replayer_hooks() {
|
fn block_replayer_hooks() {
|
||||||
let db_path = tempdir().unwrap();
|
let db_path = tempdir().unwrap();
|
||||||
|
|||||||
@@ -416,42 +416,6 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
|||||||
.map(|state| (state_root, state)))
|
.map(|state| (state_root, state)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a state from the store, but don't compute all of the values when replaying blocks
|
|
||||||
/// upon that state (e.g., state roots). Additionally, only states from the hot store are
|
|
||||||
/// returned.
|
|
||||||
///
|
|
||||||
/// See `Self::get_state` for information about `slot`.
|
|
||||||
///
|
|
||||||
/// ## Warning
|
|
||||||
///
|
|
||||||
/// The returned state **is not a valid beacon state**, it can only be used for obtaining
|
|
||||||
/// shuffling to process attestations. At least the following components of the state will be
|
|
||||||
/// broken/invalid:
|
|
||||||
///
|
|
||||||
/// - `state.state_roots`
|
|
||||||
/// - `state.block_roots`
|
|
||||||
// FIXME(sproul): delete this whole function
|
|
||||||
pub fn get_inconsistent_state_for_attestation_verification_only(
|
|
||||||
&self,
|
|
||||||
state_root: &Hash256,
|
|
||||||
slot: Option<Slot>,
|
|
||||||
) -> Result<Option<BeaconState<E>>, Error> {
|
|
||||||
metrics::inc_counter(&metrics::BEACON_STATE_GET_COUNT);
|
|
||||||
|
|
||||||
let split_slot = self.get_split_slot();
|
|
||||||
|
|
||||||
if slot.map_or(false, |slot| slot < split_slot) {
|
|
||||||
Err(HotColdDBError::AttestationStateIsFinalized {
|
|
||||||
split_slot,
|
|
||||||
request_slot: slot,
|
|
||||||
state_root: *state_root,
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
} else {
|
|
||||||
self.get_hot_state(state_root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete a state, ensuring it is removed from the LRU cache, as well as from on-disk.
|
/// Delete a state, ensuring it is removed from the LRU cache, as well as from on-disk.
|
||||||
///
|
///
|
||||||
/// It is assumed that all states being deleted reside in the hot DB, even if their slot is less
|
/// It is assumed that all states being deleted reside in the hot DB, even if their slot is less
|
||||||
|
|||||||
Reference in New Issue
Block a user