Merge branch 'remove-into-gossip-verified-block' of https://github.com/realbigsean/lighthouse into merge-unstable-deneb-june-6th
This commit is contained in:
@@ -2664,6 +2664,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
signature_verified_block.block_root(),
|
||||
signature_verified_block,
|
||||
notify_execution_layer,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -2783,6 +2784,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
block_root: Hash256,
|
||||
unverified_block: B,
|
||||
notify_execution_layer: NotifyExecutionLayer,
|
||||
publish_fn: impl FnOnce() -> Result<(), BlockError<T::EthSpec>> + Send + 'static,
|
||||
) -> Result<AvailabilityProcessingStatus, BlockError<T::EthSpec>> {
|
||||
// Start the Prometheus timer.
|
||||
let _full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);
|
||||
@@ -2798,6 +2800,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
notify_execution_layer,
|
||||
)?;
|
||||
|
||||
//TODO(sean) error handling?
|
||||
publish_fn()?;
|
||||
|
||||
let executed_block = self
|
||||
.clone()
|
||||
.into_executed_block(execution_pending)
|
||||
@@ -3073,7 +3078,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
block_delay,
|
||||
&state,
|
||||
payload_verification_status,
|
||||
self.config.progressive_balances_mode,
|
||||
&self.spec,
|
||||
&self.log,
|
||||
)
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
|
||||
}
|
||||
@@ -6012,13 +6019,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// Since we are likely calling this during the slot we are going to propose in, don't take into
|
||||
/// account the current slot when accounting for skips.
|
||||
pub fn is_healthy(&self, parent_root: &Hash256) -> Result<ChainHealth, Error> {
|
||||
let cached_head = self.canonical_head.cached_head();
|
||||
// Check if the merge has been finalized.
|
||||
if let Some(finalized_hash) = self
|
||||
.canonical_head
|
||||
.cached_head()
|
||||
.forkchoice_update_parameters()
|
||||
.finalized_hash
|
||||
{
|
||||
if let Some(finalized_hash) = cached_head.forkchoice_update_parameters().finalized_hash {
|
||||
if ExecutionBlockHash::zero() == finalized_hash {
|
||||
return Ok(ChainHealth::PreMerge);
|
||||
}
|
||||
@@ -6045,17 +6048,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
|
||||
// Check slots at the head of the chain.
|
||||
let prev_slot = current_slot.saturating_sub(Slot::new(1));
|
||||
let head_skips = prev_slot.saturating_sub(self.canonical_head.cached_head().head_slot());
|
||||
let head_skips = prev_slot.saturating_sub(cached_head.head_slot());
|
||||
let head_skips_check = head_skips.as_usize() <= self.config.builder_fallback_skips;
|
||||
|
||||
// Check if finalization is advancing.
|
||||
let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
let epochs_since_finalization = current_epoch.saturating_sub(
|
||||
self.canonical_head
|
||||
.cached_head()
|
||||
.finalized_checkpoint()
|
||||
.epoch,
|
||||
);
|
||||
let epochs_since_finalization =
|
||||
current_epoch.saturating_sub(cached_head.finalized_checkpoint().epoch);
|
||||
let finalization_check = epochs_since_finalization.as_usize()
|
||||
<= self.config.builder_fallback_epochs_since_finalization;
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ use crate::execution_payload::{
|
||||
is_optimistic_candidate_block, validate_execution_payload_for_gossip, validate_merge_block,
|
||||
AllowOptimisticImport, NotifyExecutionLayer, PayloadNotifier,
|
||||
};
|
||||
use crate::observed_block_producers::SeenBlock;
|
||||
use crate::snapshot_cache::PreProcessingSnapshot;
|
||||
use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS;
|
||||
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
|
||||
@@ -189,13 +190,6 @@ pub enum BlockError<T: EthSpec> {
|
||||
///
|
||||
/// The block is valid and we have already imported a block with this hash.
|
||||
BlockIsAlreadyKnown,
|
||||
/// A block for this proposer and slot has already been observed.
|
||||
///
|
||||
/// ## Peer scoring
|
||||
///
|
||||
/// The `proposer` has already proposed a block at this slot. The existing block may or may not
|
||||
/// be equal to the given block.
|
||||
RepeatProposal { proposer: u64, slot: Slot },
|
||||
/// The block slot exceeds the MAXIMUM_BLOCK_SLOT_NUMBER.
|
||||
///
|
||||
/// ## Peer scoring
|
||||
@@ -291,6 +285,14 @@ pub enum BlockError<T: EthSpec> {
|
||||
/// problems to worry about than losing peers, and we're doing the network a favour by
|
||||
/// disconnecting.
|
||||
ParentExecutionPayloadInvalid { parent_root: Hash256 },
|
||||
/// The block is a slashable equivocation from the proposer.
|
||||
///
|
||||
/// ## Peer scoring
|
||||
///
|
||||
/// Honest peers shouldn't forward more than 1 equivocating block from the same proposer, so
|
||||
/// we penalise them with a mid-tolerance error.
|
||||
Slashable,
|
||||
//TODO(sean) peer scoring docs
|
||||
/// A blob alone failed validation.
|
||||
BlobValidation(BlobError<T>),
|
||||
/// The block and blob together failed validation.
|
||||
@@ -892,19 +894,6 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
return Err(BlockError::BlockIsAlreadyKnown);
|
||||
}
|
||||
|
||||
// Check that we have not already received a block with a valid signature for this slot.
|
||||
if chain
|
||||
.observed_block_producers
|
||||
.read()
|
||||
.proposer_has_been_observed(block.message())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||
{
|
||||
return Err(BlockError::RepeatProposal {
|
||||
proposer: block.message().proposer_index(),
|
||||
slot: block.slot(),
|
||||
});
|
||||
}
|
||||
|
||||
// Do not process a block that doesn't descend from the finalized root.
|
||||
//
|
||||
// We check this *before* we load the parent so that we can return a more detailed error.
|
||||
@@ -1020,17 +1009,16 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
//
|
||||
// It's important to double-check that the proposer still hasn't been observed so we don't
|
||||
// have a race-condition when verifying two blocks simultaneously.
|
||||
if chain
|
||||
match chain
|
||||
.observed_block_producers
|
||||
.write()
|
||||
.observe_proposer(block.message())
|
||||
.observe_proposal(block_root, block.message())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||
{
|
||||
return Err(BlockError::RepeatProposal {
|
||||
proposer: block.message().proposer_index(),
|
||||
slot: block.slot(),
|
||||
});
|
||||
}
|
||||
SeenBlock::Slashable => return Err(BlockError::Slashable),
|
||||
SeenBlock::Duplicate => return Err(BlockError::BlockIsAlreadyKnown),
|
||||
SeenBlock::UniqueNonSlashable => {}
|
||||
};
|
||||
|
||||
if block.message().proposer_index() != expected_proposer as u64 {
|
||||
return Err(BlockError::IncorrectBlockProposer {
|
||||
@@ -1293,6 +1281,12 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
notify_execution_layer: NotifyExecutionLayer,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
chain
|
||||
.observed_block_producers
|
||||
.write()
|
||||
.observe_proposal(block_root, block.message())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
|
||||
|
||||
if let Some(parent) = chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
|
||||
@@ -343,7 +343,7 @@ where
|
||||
let beacon_block = genesis_block(&mut beacon_state, &self.spec)?;
|
||||
|
||||
beacon_state
|
||||
.build_all_caches(&self.spec)
|
||||
.build_caches(&self.spec)
|
||||
.map_err(|e| format!("Failed to build genesis state caches: {:?}", e))?;
|
||||
|
||||
let beacon_state_root = beacon_block.message().state_root();
|
||||
@@ -433,7 +433,7 @@ where
|
||||
// Prime all caches before storing the state in the database and computing the tree hash
|
||||
// root.
|
||||
weak_subj_state
|
||||
.build_all_caches(&self.spec)
|
||||
.build_caches(&self.spec)
|
||||
.map_err(|e| format!("Error building caches on checkpoint state: {e:?}"))?;
|
||||
weak_subj_state
|
||||
.update_tree_hash_cache()
|
||||
@@ -701,6 +701,8 @@ where
|
||||
store.clone(),
|
||||
Some(current_slot),
|
||||
&self.spec,
|
||||
self.chain_config.progressive_balances_mode,
|
||||
&log,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -714,7 +716,7 @@ where
|
||||
|
||||
head_snapshot
|
||||
.beacon_state
|
||||
.build_all_caches(&self.spec)
|
||||
.build_caches(&self.spec)
|
||||
.map_err(|e| format!("Failed to build state caches: {:?}", e))?;
|
||||
|
||||
// Perform a check to ensure that the finalization points of the head and fork choice are
|
||||
@@ -840,9 +842,7 @@ where
|
||||
observed_sync_aggregators: <_>::default(),
|
||||
// TODO: allow for persisting and loading the pool from disk.
|
||||
observed_block_producers: <_>::default(),
|
||||
// TODO: allow for persisting and loading the pool from disk.
|
||||
observed_blob_sidecars: <_>::default(),
|
||||
// TODO: allow for persisting and loading the pool from disk.
|
||||
observed_voluntary_exits: <_>::default(),
|
||||
observed_proposer_slashings: <_>::default(),
|
||||
observed_attester_slashings: <_>::default(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use types::{Checkpoint, Epoch};
|
||||
use types::{Checkpoint, Epoch, ProgressiveBalancesMode};
|
||||
|
||||
pub const DEFAULT_RE_ORG_THRESHOLD: ReOrgThreshold = ReOrgThreshold(20);
|
||||
pub const DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION: Epoch = Epoch::new(2);
|
||||
@@ -81,6 +81,8 @@ pub struct ChainConfig {
|
||||
pub always_prepare_payload: bool,
|
||||
/// Whether backfill sync processing should be rate-limited.
|
||||
pub enable_backfill_rate_limiting: bool,
|
||||
/// Whether to use `ProgressiveBalancesCache` in unrealized FFG progression calculation.
|
||||
pub progressive_balances_mode: ProgressiveBalancesMode,
|
||||
}
|
||||
|
||||
impl Default for ChainConfig {
|
||||
@@ -111,6 +113,7 @@ impl Default for ChainConfig {
|
||||
genesis_backfill: false,
|
||||
always_prepare_payload: false,
|
||||
enable_backfill_rate_limiting: true,
|
||||
progressive_balances_mode: ProgressiveBalancesMode::Checked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ pub enum BeaconChainError {
|
||||
BlsToExecutionConflictsWithPool,
|
||||
InconsistentFork(InconsistentFork),
|
||||
ProposerHeadForkChoiceError(fork_choice::Error<proto_array::Error>),
|
||||
UnableToPublish,
|
||||
AvailabilityCheckError(AvailabilityCheckError),
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ use state_processing::{
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use store::{iter::ParentRootBlockIterator, HotColdDB, ItemStore};
|
||||
use types::{BeaconState, ChainSpec, EthSpec, ForkName, Hash256, SignedBeaconBlock, Slot};
|
||||
use types::{
|
||||
BeaconState, ChainSpec, EthSpec, ForkName, Hash256, ProgressiveBalancesMode, SignedBeaconBlock,
|
||||
Slot,
|
||||
};
|
||||
|
||||
const CORRUPT_DB_MESSAGE: &str = "The database could be corrupt. Check its file permissions or \
|
||||
consider deleting it by running with the --purge-db flag.";
|
||||
@@ -100,6 +103,8 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
|
||||
store: Arc<HotColdDB<E, Hot, Cold>>,
|
||||
current_slot: Option<Slot>,
|
||||
spec: &ChainSpec,
|
||||
progressive_balances_mode: ProgressiveBalancesMode,
|
||||
log: &Logger,
|
||||
) -> Result<ForkChoice<BeaconForkChoiceStore<E, Hot, Cold>, E>, String> {
|
||||
// Fetch finalized block.
|
||||
let finalized_checkpoint = head_state.finalized_checkpoint();
|
||||
@@ -197,7 +202,9 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
payload_verification_status,
|
||||
progressive_balances_mode,
|
||||
spec,
|
||||
log,
|
||||
)
|
||||
.map_err(|e| format!("Error applying replayed block to fork choice: {:?}", e))?;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! Provides the `ObservedBlockProducers` struct which allows for rejecting gossip blocks from
|
||||
//! validators that have already produced a block.
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::marker::PhantomData;
|
||||
use types::{BeaconBlockRef, Epoch, EthSpec, Slot, Unsigned};
|
||||
use types::{BeaconBlockRef, Epoch, EthSpec, Hash256, Slot, Unsigned};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
@@ -14,6 +15,12 @@ pub enum Error {
|
||||
ValidatorIndexTooHigh(u64),
|
||||
}
|
||||
|
||||
#[derive(Eq, Hash, PartialEq, Debug, Default)]
|
||||
struct ProposalKey {
|
||||
slot: Slot,
|
||||
proposer: u64,
|
||||
}
|
||||
|
||||
/// Maintains a cache of observed `(block.slot, block.proposer)`.
|
||||
///
|
||||
/// The cache supports pruning based upon the finalized epoch. It does not automatically prune, you
|
||||
@@ -27,7 +34,7 @@ pub enum Error {
|
||||
/// known_distinct_shufflings` which is much smaller.
|
||||
pub struct ObservedBlockProducers<E: EthSpec> {
|
||||
finalized_slot: Slot,
|
||||
items: HashMap<Slot, HashSet<u64>>,
|
||||
items: HashMap<ProposalKey, HashSet<Hash256>>,
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
@@ -42,6 +49,24 @@ impl<E: EthSpec> Default for ObservedBlockProducers<E> {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SeenBlock {
|
||||
Duplicate,
|
||||
Slashable,
|
||||
UniqueNonSlashable,
|
||||
}
|
||||
|
||||
impl SeenBlock {
|
||||
pub fn proposer_previously_observed(self) -> bool {
|
||||
match self {
|
||||
Self::Duplicate | Self::Slashable => true,
|
||||
Self::UniqueNonSlashable => false,
|
||||
}
|
||||
}
|
||||
pub fn is_slashable(&self) -> bool {
|
||||
matches!(self, Self::Slashable)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ObservedBlockProducers<E> {
|
||||
/// Observe that the `block` was produced by `block.proposer_index` at `block.slot`. This will
|
||||
/// update `self` so future calls to it indicate that this block is known.
|
||||
@@ -52,16 +77,44 @@ impl<E: EthSpec> ObservedBlockProducers<E> {
|
||||
///
|
||||
/// - `block.proposer_index` is greater than `VALIDATOR_REGISTRY_LIMIT`.
|
||||
/// - `block.slot` is equal to or less than the latest pruned `finalized_slot`.
|
||||
pub fn observe_proposer(&mut self, block: BeaconBlockRef<'_, E>) -> Result<bool, Error> {
|
||||
pub fn observe_proposal(
|
||||
&mut self,
|
||||
block_root: Hash256,
|
||||
block: BeaconBlockRef<'_, E>,
|
||||
) -> Result<SeenBlock, Error> {
|
||||
self.sanitize_block(block)?;
|
||||
|
||||
let did_not_exist = self
|
||||
.items
|
||||
.entry(block.slot())
|
||||
.or_insert_with(|| HashSet::with_capacity(E::SlotsPerEpoch::to_usize()))
|
||||
.insert(block.proposer_index());
|
||||
let key = ProposalKey {
|
||||
slot: block.slot(),
|
||||
proposer: block.proposer_index(),
|
||||
};
|
||||
|
||||
Ok(!did_not_exist)
|
||||
let entry = self.items.entry(key);
|
||||
|
||||
let slashable_proposal = match entry {
|
||||
Entry::Occupied(mut occupied_entry) => {
|
||||
let block_roots = occupied_entry.get_mut();
|
||||
let newly_inserted = block_roots.insert(block_root);
|
||||
|
||||
let is_equivocation = block_roots.len() > 1;
|
||||
|
||||
if is_equivocation {
|
||||
SeenBlock::Slashable
|
||||
} else if !newly_inserted {
|
||||
SeenBlock::Duplicate
|
||||
} else {
|
||||
SeenBlock::UniqueNonSlashable
|
||||
}
|
||||
}
|
||||
Entry::Vacant(vacant_entry) => {
|
||||
let block_roots = HashSet::from([block_root]);
|
||||
vacant_entry.insert(block_roots);
|
||||
|
||||
SeenBlock::UniqueNonSlashable
|
||||
}
|
||||
};
|
||||
|
||||
Ok(slashable_proposal)
|
||||
}
|
||||
|
||||
/// Returns `Ok(true)` if the `block` has been observed before, `Ok(false)` if not. Does not
|
||||
@@ -72,15 +125,33 @@ impl<E: EthSpec> ObservedBlockProducers<E> {
|
||||
///
|
||||
/// - `block.proposer_index` is greater than `VALIDATOR_REGISTRY_LIMIT`.
|
||||
/// - `block.slot` is equal to or less than the latest pruned `finalized_slot`.
|
||||
pub fn proposer_has_been_observed(&self, block: BeaconBlockRef<'_, E>) -> Result<bool, Error> {
|
||||
pub fn proposer_has_been_observed(
|
||||
&self,
|
||||
block: BeaconBlockRef<'_, E>,
|
||||
block_root: Hash256,
|
||||
) -> Result<SeenBlock, Error> {
|
||||
self.sanitize_block(block)?;
|
||||
|
||||
let exists = self
|
||||
.items
|
||||
.get(&block.slot())
|
||||
.map_or(false, |set| set.contains(&block.proposer_index()));
|
||||
let key = ProposalKey {
|
||||
slot: block.slot(),
|
||||
proposer: block.proposer_index(),
|
||||
};
|
||||
|
||||
Ok(exists)
|
||||
if let Some(block_roots) = self.items.get(&key) {
|
||||
let block_already_known = block_roots.contains(&block_root);
|
||||
let no_prev_known_blocks =
|
||||
block_roots.difference(&HashSet::from([block_root])).count() == 0;
|
||||
|
||||
if !no_prev_known_blocks {
|
||||
Ok(SeenBlock::Slashable)
|
||||
} else if block_already_known {
|
||||
Ok(SeenBlock::Duplicate)
|
||||
} else {
|
||||
Ok(SeenBlock::UniqueNonSlashable)
|
||||
}
|
||||
} else {
|
||||
Ok(SeenBlock::UniqueNonSlashable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if the given `block` is sane.
|
||||
@@ -112,15 +183,15 @@ impl<E: EthSpec> ObservedBlockProducers<E> {
|
||||
}
|
||||
|
||||
self.finalized_slot = finalized_slot;
|
||||
self.items.retain(|slot, _set| *slot > finalized_slot);
|
||||
self.items.retain(|key, _| key.slot > finalized_slot);
|
||||
}
|
||||
|
||||
/// Returns `true` if the given `validator_index` has been stored in `self` at `epoch`.
|
||||
///
|
||||
/// This is useful for doppelganger detection.
|
||||
pub fn index_seen_at_epoch(&self, validator_index: u64, epoch: Epoch) -> bool {
|
||||
self.items.iter().any(|(slot, producers)| {
|
||||
slot.epoch(E::slots_per_epoch()) == epoch && producers.contains(&validator_index)
|
||||
self.items.iter().any(|(key, _)| {
|
||||
key.slot.epoch(E::slots_per_epoch()) == epoch && key.proposer == validator_index
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -148,9 +219,12 @@ mod tests {
|
||||
|
||||
// Slot 0, proposer 0
|
||||
let block_a = get_block(0, 0);
|
||||
let block_root = block_a.canonical_root();
|
||||
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_a.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root, block_a.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(false),
|
||||
"can observe proposer, indicates proposer unobserved"
|
||||
);
|
||||
@@ -164,7 +238,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(0))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(0),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -182,7 +259,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(0))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(0),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -207,9 +287,12 @@ mod tests {
|
||||
|
||||
// First slot of finalized epoch, proposer 0
|
||||
let block_b = get_block(E::slots_per_epoch(), 0);
|
||||
let block_root_b = block_b.canonical_root();
|
||||
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_b.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_b, block_b.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Err(Error::FinalizedBlock {
|
||||
slot: E::slots_per_epoch().into(),
|
||||
finalized_slot: E::slots_per_epoch().into(),
|
||||
@@ -229,7 +312,9 @@ mod tests {
|
||||
let block_b = get_block(three_epochs, 0);
|
||||
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_b.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_b, block_b.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(false),
|
||||
"can insert non-finalized block"
|
||||
);
|
||||
@@ -238,7 +323,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(three_epochs))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(three_epochs),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("the three epochs slot should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -262,7 +350,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(three_epochs))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(three_epochs),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("the three epochs slot should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -276,24 +367,33 @@ mod tests {
|
||||
|
||||
// Slot 0, proposer 0
|
||||
let block_a = get_block(0, 0);
|
||||
let block_root_a = block_a.canonical_root();
|
||||
|
||||
assert_eq!(
|
||||
cache.proposer_has_been_observed(block_a.to_ref()),
|
||||
cache
|
||||
.proposer_has_been_observed(block_a.to_ref(), block_a.canonical_root())
|
||||
.map(|x| x.proposer_previously_observed()),
|
||||
Ok(false),
|
||||
"no observation in empty cache"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_a.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_a, block_a.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(false),
|
||||
"can observe proposer, indicates proposer unobserved"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.proposer_has_been_observed(block_a.to_ref()),
|
||||
cache
|
||||
.proposer_has_been_observed(block_a.to_ref(), block_a.canonical_root())
|
||||
.map(|x| x.proposer_previously_observed()),
|
||||
Ok(true),
|
||||
"observed block is indicated as true"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_a.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_a, block_a.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(true),
|
||||
"observing again indicates true"
|
||||
);
|
||||
@@ -303,7 +403,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(0))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(0),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -312,24 +415,33 @@ mod tests {
|
||||
|
||||
// Slot 1, proposer 0
|
||||
let block_b = get_block(1, 0);
|
||||
let block_root_b = block_b.canonical_root();
|
||||
|
||||
assert_eq!(
|
||||
cache.proposer_has_been_observed(block_b.to_ref()),
|
||||
cache
|
||||
.proposer_has_been_observed(block_b.to_ref(), block_b.canonical_root())
|
||||
.map(|x| x.proposer_previously_observed()),
|
||||
Ok(false),
|
||||
"no observation for new slot"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_b.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_b, block_b.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(false),
|
||||
"can observe proposer for new slot, indicates proposer unobserved"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.proposer_has_been_observed(block_b.to_ref()),
|
||||
cache
|
||||
.proposer_has_been_observed(block_b.to_ref(), block_b.canonical_root())
|
||||
.map(|x| x.proposer_previously_observed()),
|
||||
Ok(true),
|
||||
"observed block in slot 1 is indicated as true"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_b.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_b, block_b.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(true),
|
||||
"observing slot 1 again indicates true"
|
||||
);
|
||||
@@ -339,7 +451,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(0))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(0),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -348,7 +463,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(1))
|
||||
.get(&ProposalKey {
|
||||
slot: Slot::new(1),
|
||||
proposer: 0
|
||||
})
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
1,
|
||||
@@ -357,45 +475,54 @@ mod tests {
|
||||
|
||||
// Slot 0, proposer 1
|
||||
let block_c = get_block(0, 1);
|
||||
let block_root_c = block_c.canonical_root();
|
||||
|
||||
assert_eq!(
|
||||
cache.proposer_has_been_observed(block_c.to_ref()),
|
||||
cache
|
||||
.proposer_has_been_observed(block_c.to_ref(), block_c.canonical_root())
|
||||
.map(|x| x.proposer_previously_observed()),
|
||||
Ok(false),
|
||||
"no observation for new proposer"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_c.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_c, block_c.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(false),
|
||||
"can observe new proposer, indicates proposer unobserved"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.proposer_has_been_observed(block_c.to_ref()),
|
||||
cache
|
||||
.proposer_has_been_observed(block_c.to_ref(), block_c.canonical_root())
|
||||
.map(|x| x.proposer_previously_observed()),
|
||||
Ok(true),
|
||||
"observed new proposer block is indicated as true"
|
||||
);
|
||||
assert_eq!(
|
||||
cache.observe_proposer(block_c.to_ref()),
|
||||
cache
|
||||
.observe_proposal(block_root_c, block_c.to_ref())
|
||||
.map(SeenBlock::proposer_previously_observed),
|
||||
Ok(true),
|
||||
"observing new proposer again indicates true"
|
||||
);
|
||||
|
||||
assert_eq!(cache.finalized_slot, 0, "finalized slot is zero");
|
||||
assert_eq!(cache.items.len(), 2, "two slots should be present");
|
||||
assert_eq!(cache.items.len(), 3, "three slots should be present");
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(0))
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
.iter()
|
||||
.filter(|(k, _)| k.slot == cache.finalized_slot)
|
||||
.count(),
|
||||
2,
|
||||
"two proposers should be present in slot 0"
|
||||
);
|
||||
assert_eq!(
|
||||
cache
|
||||
.items
|
||||
.get(&Slot::new(1))
|
||||
.expect("slot zero should be present")
|
||||
.len(),
|
||||
.iter()
|
||||
.filter(|(k, _)| k.slot == Slot::new(1))
|
||||
.count(),
|
||||
1,
|
||||
"only one proposer should be present in slot 1"
|
||||
);
|
||||
|
||||
@@ -808,6 +808,15 @@ where
|
||||
state.get_block_root(slot).unwrap() == state.get_block_root(slot - 1).unwrap()
|
||||
}
|
||||
|
||||
pub async fn make_blinded_block(
|
||||
&self,
|
||||
state: BeaconState<E>,
|
||||
slot: Slot,
|
||||
) -> (SignedBlindedBeaconBlock<E>, BeaconState<E>) {
|
||||
let (unblinded, new_state) = self.make_block(state, slot).await;
|
||||
(unblinded.into(), new_state)
|
||||
}
|
||||
|
||||
/// Returns a newly created block, signed by the proposer for the given slot.
|
||||
pub async fn make_block(
|
||||
&self,
|
||||
@@ -820,9 +829,7 @@ where
|
||||
complete_state_advance(&mut state, None, slot, &self.spec)
|
||||
.expect("should be able to advance state to slot");
|
||||
|
||||
state
|
||||
.build_all_caches(&self.spec)
|
||||
.expect("should build caches");
|
||||
state.build_caches(&self.spec).expect("should build caches");
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(slot, &self.spec).unwrap();
|
||||
|
||||
@@ -906,9 +913,7 @@ where
|
||||
complete_state_advance(&mut state, None, slot, &self.spec)
|
||||
.expect("should be able to advance state to slot");
|
||||
|
||||
state
|
||||
.build_all_caches(&self.spec)
|
||||
.expect("should build caches");
|
||||
state.build_caches(&self.spec).expect("should build caches");
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(slot, &self.spec).unwrap();
|
||||
|
||||
@@ -1626,6 +1631,36 @@ where
|
||||
.sign(sk, &fork, genesis_validators_root, &self.chain.spec)
|
||||
}
|
||||
|
||||
pub fn add_proposer_slashing(&self, validator_index: u64) -> Result<(), String> {
|
||||
let propposer_slashing = self.make_proposer_slashing(validator_index);
|
||||
if let ObservationOutcome::New(verified_proposer_slashing) = self
|
||||
.chain
|
||||
.verify_proposer_slashing_for_gossip(propposer_slashing)
|
||||
.expect("should verify proposer slashing for gossip")
|
||||
{
|
||||
self.chain
|
||||
.import_proposer_slashing(verified_proposer_slashing);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("should observe new proposer slashing".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_attester_slashing(&self, validator_indices: Vec<u64>) -> Result<(), String> {
|
||||
let attester_slashing = self.make_attester_slashing(validator_indices);
|
||||
if let ObservationOutcome::New(verified_attester_slashing) = self
|
||||
.chain
|
||||
.verify_attester_slashing_for_gossip(attester_slashing)
|
||||
.expect("should verify attester slashing for gossip")
|
||||
{
|
||||
self.chain
|
||||
.import_attester_slashing(verified_attester_slashing);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("should observe new attester slashing".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_bls_to_execution_change(
|
||||
&self,
|
||||
validator_index: u64,
|
||||
@@ -1804,7 +1839,7 @@ where
|
||||
self.set_current_slot(slot);
|
||||
let block_hash: SignedBeaconBlockHash = self
|
||||
.chain
|
||||
.process_block(block_root, block.into(), NotifyExecutionLayer::Yes)
|
||||
.process_block(block_root, block.into(), NotifyExecutionLayer::Yes,|| Ok(()),)
|
||||
.await?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
@@ -1823,6 +1858,7 @@ where
|
||||
wrapped_block.canonical_root(),
|
||||
wrapped_block,
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await?
|
||||
.try_into()
|
||||
|
||||
@@ -459,6 +459,7 @@ async fn assert_invalid_signature(
|
||||
chain_segment_blobs[block_index].clone(),
|
||||
),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
@@ -526,6 +527,7 @@ async fn invalid_signature_gossip_block() {
|
||||
signed_block.canonical_root(),
|
||||
Arc::new(signed_block),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await,
|
||||
Err(BlockError::InvalidSignature)
|
||||
@@ -859,6 +861,7 @@ async fn block_gossip_verification() {
|
||||
gossip_verified.block_root,
|
||||
gossip_verified,
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.expect("should import valid gossip verified block");
|
||||
@@ -1069,11 +1072,7 @@ async fn block_gossip_verification() {
|
||||
assert!(
|
||||
matches!(
|
||||
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone()).into()).await),
|
||||
BlockError::RepeatProposal {
|
||||
proposer,
|
||||
slot,
|
||||
}
|
||||
if proposer == other_proposer && slot == block.message().slot()
|
||||
BlockError::BlockIsAlreadyKnown,
|
||||
),
|
||||
"should register any valid signature against the proposer, even if the block failed later verification"
|
||||
);
|
||||
@@ -1106,11 +1105,7 @@ async fn block_gossip_verification() {
|
||||
.await
|
||||
.err()
|
||||
.expect("should error when processing known block"),
|
||||
BlockError::RepeatProposal {
|
||||
proposer,
|
||||
slot,
|
||||
}
|
||||
if proposer == block.message().proposer_index() && slot == block.message().slot()
|
||||
BlockError::BlockIsAlreadyKnown
|
||||
),
|
||||
"the second proposal by this validator should be rejected"
|
||||
);
|
||||
@@ -1159,6 +1154,7 @@ async fn verify_block_for_gossip_slashing_detection() {
|
||||
verified_block.block_root,
|
||||
verified_block,
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1198,6 +1194,7 @@ async fn verify_block_for_gossip_doppelganger_detection() {
|
||||
verified_block.block_root,
|
||||
verified_block,
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1345,6 +1342,7 @@ async fn add_base_block_to_altair_chain() {
|
||||
base_block.canonical_root(),
|
||||
Arc::new(base_block.clone()),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
@@ -1479,6 +1477,7 @@ async fn add_altair_block_to_base_chain() {
|
||||
altair_block.canonical_root(),
|
||||
Arc::new(altair_block.clone()),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
|
||||
@@ -133,13 +133,8 @@ async fn base_altair_merge_capella() {
|
||||
for _ in (merge_fork_slot.as_u64() + 3)..capella_fork_slot.as_u64() {
|
||||
harness.extend_slots(1).await;
|
||||
let block = &harness.chain.head_snapshot().beacon_block;
|
||||
let full_payload: FullPayload<E> = block
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into();
|
||||
let full_payload: FullPayload<E> =
|
||||
block.message().body().execution_payload().unwrap().into();
|
||||
// pre-capella shouldn't have withdrawals
|
||||
assert!(full_payload.withdrawals_root().is_err());
|
||||
execution_payloads.push(full_payload);
|
||||
@@ -151,13 +146,8 @@ async fn base_altair_merge_capella() {
|
||||
for _ in 0..16 {
|
||||
harness.extend_slots(1).await;
|
||||
let block = &harness.chain.head_snapshot().beacon_block;
|
||||
let full_payload: FullPayload<E> = block
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into();
|
||||
let full_payload: FullPayload<E> =
|
||||
block.message().body().execution_payload().unwrap().into();
|
||||
// post-capella should have withdrawals
|
||||
assert!(full_payload.withdrawals_root().is_ok());
|
||||
execution_payloads.push(full_payload);
|
||||
|
||||
@@ -698,6 +698,7 @@ async fn invalidates_all_descendants() {
|
||||
fork_block.canonical_root(),
|
||||
Arc::new(fork_block),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -797,6 +798,7 @@ async fn switches_heads() {
|
||||
fork_block.canonical_root(),
|
||||
Arc::new(fork_block),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -1055,7 +1057,9 @@ async fn invalid_parent() {
|
||||
|
||||
// Ensure the block built atop an invalid payload is invalid for import.
|
||||
assert!(matches!(
|
||||
rig.harness.chain.process_block(block.canonical_root(), block.clone(), NotifyExecutionLayer::Yes).await,
|
||||
rig.harness.chain.process_block(block.canonical_root(), block.clone(), NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
).await,
|
||||
Err(BlockError::ParentExecutionPayloadInvalid { parent_root: invalid_root })
|
||||
if invalid_root == parent_root
|
||||
));
|
||||
@@ -1069,8 +1073,9 @@ async fn invalid_parent() {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Optimistic,
|
||||
rig.harness.chain.config.progressive_balances_mode,
|
||||
&rig.harness.chain.spec,
|
||||
|
||||
rig.harness.logger()
|
||||
),
|
||||
Err(ForkChoiceError::ProtoArrayStringError(message))
|
||||
if message.contains(&format!(
|
||||
@@ -1341,7 +1346,12 @@ async fn build_optimistic_chain(
|
||||
for block in blocks {
|
||||
rig.harness
|
||||
.chain
|
||||
.process_block(block.canonical_root(), block, NotifyExecutionLayer::Yes)
|
||||
.process_block(
|
||||
block.canonical_root(),
|
||||
block,
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1901,6 +1911,7 @@ async fn recover_from_invalid_head_by_importing_blocks() {
|
||||
fork_block.canonical_root(),
|
||||
fork_block.clone(),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -2178,6 +2178,7 @@ async fn weak_subjectivity_sync() {
|
||||
full_block.canonical_root(),
|
||||
BlockWrapper::new(Arc::new(full_block), blobs),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -686,6 +686,7 @@ async fn run_skip_slot_test(skip_slots: u64) {
|
||||
harness_a.chain.head_snapshot().beacon_block_root,
|
||||
harness_a.get_head_block(),
|
||||
NotifyExecutionLayer::Yes,
|
||||
|| Ok(())
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user