mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-01 11:54:40 +00:00
Trying something out
This commit is contained in:
@@ -4,9 +4,7 @@ use crate::attestation_verification::{
|
|||||||
batch_verify_unaggregated_attestations,
|
batch_verify_unaggregated_attestations,
|
||||||
};
|
};
|
||||||
use crate::beacon_block_streamer::{BeaconBlockStreamer, CheckCaches};
|
use crate::beacon_block_streamer::{BeaconBlockStreamer, CheckCaches};
|
||||||
use crate::beacon_proposer_cache::{
|
use crate::beacon_proposer_cache::{BeaconProposerCache, EpochBlockProposers};
|
||||||
BeaconProposerCache, EpochBlockProposers, ensure_state_can_determine_proposers_for_epoch,
|
|
||||||
};
|
|
||||||
use crate::blob_verification::{GossipBlobError, GossipVerifiedBlob};
|
use crate::blob_verification::{GossipBlobError, GossipVerifiedBlob};
|
||||||
use crate::block_times_cache::BlockTimesCache;
|
use crate::block_times_cache::BlockTimesCache;
|
||||||
use crate::block_verification::POS_PANDA_BANNER;
|
use crate::block_verification::POS_PANDA_BANNER;
|
||||||
@@ -6548,62 +6546,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
accessor: impl Fn(&EpochBlockProposers) -> Result<V, BeaconChainError>,
|
accessor: impl Fn(&EpochBlockProposers) -> Result<V, BeaconChainError>,
|
||||||
state_provider: impl FnOnce() -> Result<(Hash256, BeaconState<T::EthSpec>), E>,
|
state_provider: impl FnOnce() -> Result<(Hash256, BeaconState<T::EthSpec>), E>,
|
||||||
) -> Result<V, E> {
|
) -> Result<V, E> {
|
||||||
let cache_entry = self
|
crate::beacon_proposer_cache::with_proposer_cache(
|
||||||
.beacon_proposer_cache
|
&self.beacon_proposer_cache,
|
||||||
.lock()
|
|
||||||
.get_or_insert_key(proposal_epoch, shuffling_decision_block);
|
|
||||||
|
|
||||||
// If the cache entry is not initialised, run the code to initialise it inside a OnceCell.
|
|
||||||
// This prevents duplication of work across multiple threads.
|
|
||||||
//
|
|
||||||
// If it is already initialised, then `get_or_try_init` will return immediately without
|
|
||||||
// executing the initialisation code at all.
|
|
||||||
let epoch_block_proposers = cache_entry.get_or_try_init(|| {
|
|
||||||
// Fetch the state on-demand if the required epoch was missing from the cache.
|
|
||||||
// If the caller wants to not compute the state they must return an error here and then
|
|
||||||
// catch it at the call site.
|
|
||||||
let (state_root, mut state) = state_provider()?;
|
|
||||||
|
|
||||||
// Ensure the state can compute proposer duties for `epoch`.
|
|
||||||
ensure_state_can_determine_proposers_for_epoch(
|
|
||||||
&mut state,
|
|
||||||
state_root,
|
|
||||||
proposal_epoch,
|
|
||||||
&self.spec,
|
&self.spec,
|
||||||
)?;
|
shuffling_decision_block,
|
||||||
|
|
||||||
// Sanity check the state.
|
|
||||||
let latest_block_root = state.get_latest_block_root(state_root);
|
|
||||||
let state_decision_block_root = state.proposer_shuffling_decision_root_at_epoch(
|
|
||||||
proposal_epoch,
|
proposal_epoch,
|
||||||
latest_block_root,
|
accessor,
|
||||||
&self.spec,
|
state_provider,
|
||||||
)?;
|
)
|
||||||
if state_decision_block_root != shuffling_decision_block {
|
|
||||||
return Err(Error::ProposerCacheIncorrectState {
|
|
||||||
state_decision_block_root,
|
|
||||||
requested_decision_block_root: shuffling_decision_block,
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let proposers = state.get_beacon_proposer_indices(proposal_epoch, &self.spec)?;
|
|
||||||
|
|
||||||
// Use fork_at_epoch rather than the state's fork, because post-Fulu we may not have
|
|
||||||
// advanced the state completely into the new epoch.
|
|
||||||
let fork = self.spec.fork_at_epoch(proposal_epoch);
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
?shuffling_decision_block,
|
|
||||||
epoch = %proposal_epoch,
|
|
||||||
"Priming proposer shuffling cache"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok::<_, E>(EpochBlockProposers::new(proposal_epoch, fork, proposers))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Run the accessor function on the computed epoch proposers.
|
|
||||||
accessor(epoch_block_proposers).map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the `map_fn` with the committee cache for `shuffling_epoch` from the chain with head
|
/// Runs the `map_fn` with the committee cache for `shuffling_epoch` from the chain with head
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
|||||||
use fork_choice::ExecutionStatus;
|
use fork_choice::ExecutionStatus;
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use safe_arith::SafeArith;
|
use safe_arith::SafeArith;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use state_processing::state_advance::partial_state_advance;
|
use state_processing::state_advance::partial_state_advance;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::{debug, instrument};
|
||||||
use typenum::Unsigned;
|
use typenum::Unsigned;
|
||||||
use types::new_non_zero_usize;
|
use types::new_non_zero_usize;
|
||||||
use types::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, Fork, Hash256, Slot};
|
use types::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, Fork, Hash256, Slot};
|
||||||
@@ -164,6 +165,82 @@ impl BeaconProposerCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the proposer cache, computing and caching the proposers if necessary.
|
||||||
|
///
|
||||||
|
/// This is a free function that operates on references to the cache and spec, decoupled from
|
||||||
|
/// `BeaconChain`. The `accessor` is called with the cached `EpochBlockProposers` for the given
|
||||||
|
/// `(proposal_epoch, shuffling_decision_block)` key. If the cache entry is missing, the
|
||||||
|
/// `state_provider` closure is called to produce a state which is then used to compute and
|
||||||
|
/// cache the proposers.
|
||||||
|
pub fn with_proposer_cache<Spec, V, Err>(
|
||||||
|
beacon_proposer_cache: &Mutex<BeaconProposerCache>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
shuffling_decision_block: Hash256,
|
||||||
|
proposal_epoch: Epoch,
|
||||||
|
accessor: impl Fn(&EpochBlockProposers) -> Result<V, BeaconChainError>,
|
||||||
|
state_provider: impl FnOnce() -> Result<(Hash256, BeaconState<Spec>), Err>,
|
||||||
|
) -> Result<V, Err>
|
||||||
|
where
|
||||||
|
Spec: EthSpec,
|
||||||
|
Err: From<BeaconChainError> + From<BeaconStateError>,
|
||||||
|
{
|
||||||
|
let cache_entry = beacon_proposer_cache
|
||||||
|
.lock()
|
||||||
|
.get_or_insert_key(proposal_epoch, shuffling_decision_block);
|
||||||
|
|
||||||
|
// If the cache entry is not initialised, run the code to initialise it inside a OnceCell.
|
||||||
|
// This prevents duplication of work across multiple threads.
|
||||||
|
//
|
||||||
|
// If it is already initialised, then `get_or_try_init` will return immediately without
|
||||||
|
// executing the initialisation code at all.
|
||||||
|
let epoch_block_proposers = cache_entry.get_or_try_init(|| {
|
||||||
|
// Fetch the state on-demand if the required epoch was missing from the cache.
|
||||||
|
// If the caller wants to not compute the state they must return an error here and then
|
||||||
|
// catch it at the call site.
|
||||||
|
let (state_root, mut state) = state_provider()?;
|
||||||
|
|
||||||
|
// Ensure the state can compute proposer duties for `epoch`.
|
||||||
|
ensure_state_can_determine_proposers_for_epoch(
|
||||||
|
&mut state,
|
||||||
|
state_root,
|
||||||
|
proposal_epoch,
|
||||||
|
spec,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Sanity check the state.
|
||||||
|
let latest_block_root = state.get_latest_block_root(state_root);
|
||||||
|
let state_decision_block_root = state.proposer_shuffling_decision_root_at_epoch(
|
||||||
|
proposal_epoch,
|
||||||
|
latest_block_root,
|
||||||
|
spec,
|
||||||
|
)?;
|
||||||
|
if state_decision_block_root != shuffling_decision_block {
|
||||||
|
return Err(BeaconChainError::ProposerCacheIncorrectState {
|
||||||
|
state_decision_block_root,
|
||||||
|
requested_decision_block_root: shuffling_decision_block,
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let proposers = state.get_beacon_proposer_indices(proposal_epoch, spec)?;
|
||||||
|
|
||||||
|
// Use fork_at_epoch rather than the state's fork, because post-Fulu we may not have
|
||||||
|
// advanced the state completely into the new epoch.
|
||||||
|
let fork = spec.fork_at_epoch(proposal_epoch);
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
?shuffling_decision_block,
|
||||||
|
epoch = %proposal_epoch,
|
||||||
|
"Priming proposer shuffling cache"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok::<_, Err>(EpochBlockProposers::new(proposal_epoch, fork, proposers))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Run the accessor function on the computed epoch proposers.
|
||||||
|
accessor(epoch_block_proposers).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the proposer duties using the head state without cache.
|
/// Compute the proposer duties using the head state without cache.
|
||||||
///
|
///
|
||||||
/// Return:
|
/// Return:
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use educe::Educe;
|
use educe::Educe;
|
||||||
|
use parking_lot::{Mutex, RwLock};
|
||||||
use slot_clock::SlotClock;
|
use slot_clock::SlotClock;
|
||||||
use state_processing::{
|
use state_processing::{
|
||||||
VerifySignatures,
|
VerifySignatures,
|
||||||
envelope_processing::{VerifyStateRoot, process_execution_payload_envelope},
|
envelope_processing::{VerifyStateRoot, process_execution_payload_envelope},
|
||||||
};
|
};
|
||||||
|
use store::DatabaseBlock;
|
||||||
use tracing::{Span, debug};
|
use tracing::{Span, debug};
|
||||||
use types::{
|
use types::{
|
||||||
EthSpec, SignedBeaconBlock, SignedExecutionPayloadEnvelope,
|
ChainSpec, EthSpec, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope,
|
||||||
consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BeaconChain, BeaconChainError, BeaconChainTypes, NotifyExecutionLayer,
|
BeaconChain, BeaconChainError, BeaconChainTypes, BeaconStore, NotifyExecutionLayer,
|
||||||
PayloadVerificationOutcome,
|
PayloadVerificationOutcome,
|
||||||
|
beacon_proposer_cache::{self, BeaconProposerCache},
|
||||||
|
canonical_head::CanonicalHead,
|
||||||
payload_envelope_verification::{
|
payload_envelope_verification::{
|
||||||
EnvelopeError, EnvelopeImportData, EnvelopeProcessingSnapshot, ExecutionPendingEnvelope,
|
EnvelopeError, EnvelopeImportData, EnvelopeProcessingSnapshot, ExecutionPendingEnvelope,
|
||||||
IntoExecutionPendingEnvelope, MaybeAvailableEnvelope, load_snapshot,
|
IntoExecutionPendingEnvelope, MaybeAvailableEnvelope, load_snapshot,
|
||||||
payload_notifier::PayloadNotifier,
|
payload_notifier::PayloadNotifier,
|
||||||
},
|
},
|
||||||
|
validator_pubkey_cache::ValidatorPubkeyCache,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Bundles only the dependencies needed for gossip verification of execution payload envelopes,
|
||||||
|
/// decoupling `GossipVerifiedEnvelope::new` from the full `BeaconChain`.
|
||||||
|
pub struct GossipVerificationContext<'a, T: BeaconChainTypes> {
|
||||||
|
pub canonical_head: &'a CanonicalHead<T>,
|
||||||
|
pub store: &'a BeaconStore<T>,
|
||||||
|
pub spec: &'a ChainSpec,
|
||||||
|
pub beacon_proposer_cache: &'a Mutex<BeaconProposerCache>,
|
||||||
|
pub validator_pubkey_cache: &'a RwLock<ValidatorPubkeyCache<T>>,
|
||||||
|
pub genesis_validators_root: Hash256,
|
||||||
|
}
|
||||||
|
|
||||||
/// A wrapper around a `SignedExecutionPayloadEnvelope` that indicates it has been approved for re-gossiping on
|
/// A wrapper around a `SignedExecutionPayloadEnvelope` that indicates it has been approved for re-gossiping on
|
||||||
/// the p2p network.
|
/// the p2p network.
|
||||||
#[derive(Educe)]
|
#[derive(Educe)]
|
||||||
@@ -35,7 +51,7 @@ pub struct GossipVerifiedEnvelope<T: BeaconChainTypes> {
|
|||||||
impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
signed_envelope: Arc<SignedExecutionPayloadEnvelope<T::EthSpec>>,
|
signed_envelope: Arc<SignedExecutionPayloadEnvelope<T::EthSpec>>,
|
||||||
chain: &BeaconChain<T>,
|
ctx: &GossipVerificationContext<'_, T>,
|
||||||
) -> Result<Self, EnvelopeError> {
|
) -> Result<Self, EnvelopeError> {
|
||||||
let envelope = &signed_envelope.message;
|
let envelope = &signed_envelope.message;
|
||||||
let payload = &envelope.payload;
|
let payload = &envelope.payload;
|
||||||
@@ -48,7 +64,7 @@ impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
|||||||
// 2. Blocks we've seen that are invalid (REJECT).
|
// 2. Blocks we've seen that are invalid (REJECT).
|
||||||
//
|
//
|
||||||
// Presently these two cases are conflated.
|
// Presently these two cases are conflated.
|
||||||
let fork_choice_read_lock = chain.canonical_head.fork_choice_read_lock();
|
let fork_choice_read_lock = ctx.canonical_head.fork_choice_read_lock();
|
||||||
let Some(proto_block) = fork_choice_read_lock.get_block(&beacon_block_root) else {
|
let Some(proto_block) = fork_choice_read_lock.get_block(&beacon_block_root) else {
|
||||||
return Err(EnvelopeError::BlockRootUnknown {
|
return Err(EnvelopeError::BlockRootUnknown {
|
||||||
block_root: beacon_block_root,
|
block_root: beacon_block_root,
|
||||||
@@ -64,12 +80,14 @@ impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
|||||||
|
|
||||||
// TODO(EIP-7732): check that we haven't seen another valid `SignedExecutionPayloadEnvelope`
|
// TODO(EIP-7732): check that we haven't seen another valid `SignedExecutionPayloadEnvelope`
|
||||||
// for this block root from this builder - envelope status table check
|
// for this block root from this builder - envelope status table check
|
||||||
let block = chain
|
let block = match ctx.store.try_get_full_block(&beacon_block_root)? {
|
||||||
.get_full_block(&beacon_block_root)?
|
Some(DatabaseBlock::Full(block)) => Arc::new(block),
|
||||||
.ok_or_else(|| {
|
Some(DatabaseBlock::Blinded(_)) | None => {
|
||||||
EnvelopeError::from(BeaconChainError::MissingBeaconBlock(beacon_block_root))
|
return Err(EnvelopeError::from(BeaconChainError::MissingBeaconBlock(
|
||||||
})
|
beacon_block_root,
|
||||||
.map(Arc::new)?;
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
let execution_bid = &block
|
let execution_bid = &block
|
||||||
.message()
|
.message()
|
||||||
.body()
|
.body()
|
||||||
@@ -118,13 +136,15 @@ impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
|||||||
let block_slot = envelope.slot;
|
let block_slot = envelope.slot;
|
||||||
let block_epoch = block_slot.epoch(T::EthSpec::slots_per_epoch());
|
let block_epoch = block_slot.epoch(T::EthSpec::slots_per_epoch());
|
||||||
let proposer_shuffling_decision_block =
|
let proposer_shuffling_decision_block =
|
||||||
proto_block.proposer_shuffling_root_for_child_block(block_epoch, &chain.spec);
|
proto_block.proposer_shuffling_root_for_child_block(block_epoch, ctx.spec);
|
||||||
|
|
||||||
let (signature_is_valid, opt_snapshot) = if builder_index == BUILDER_INDEX_SELF_BUILD {
|
let (signature_is_valid, opt_snapshot) = if builder_index == BUILDER_INDEX_SELF_BUILD {
|
||||||
// Fast path: self-build envelopes can be verified without loading the state.
|
// Fast path: self-build envelopes can be verified without loading the state.
|
||||||
let envelope_ref = signed_envelope.as_ref();
|
let envelope_ref = signed_envelope.as_ref();
|
||||||
let mut opt_snapshot = None;
|
let mut opt_snapshot = None;
|
||||||
let proposer = chain.with_proposer_cache::<_, EnvelopeError>(
|
let proposer = beacon_proposer_cache::with_proposer_cache(
|
||||||
|
ctx.beacon_proposer_cache,
|
||||||
|
ctx.spec,
|
||||||
proposer_shuffling_decision_block,
|
proposer_shuffling_decision_block,
|
||||||
block_epoch,
|
block_epoch,
|
||||||
|proposers| proposers.get_slot::<T::EthSpec>(block_slot),
|
|proposers| proposers.get_slot::<T::EthSpec>(block_slot),
|
||||||
@@ -133,14 +153,14 @@ impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
|||||||
%beacon_block_root,
|
%beacon_block_root,
|
||||||
"Proposer shuffling cache miss for envelope verification"
|
"Proposer shuffling cache miss for envelope verification"
|
||||||
);
|
);
|
||||||
let snapshot = load_snapshot(envelope_ref, chain)?;
|
let snapshot = load_snapshot(envelope_ref, ctx.canonical_head, ctx.store)?;
|
||||||
opt_snapshot = Some(Box::new(snapshot.clone()));
|
opt_snapshot = Some(Box::new(snapshot.clone()));
|
||||||
Ok((snapshot.state_root, snapshot.pre_state))
|
Ok::<_, EnvelopeError>((snapshot.state_root, snapshot.pre_state))
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
let fork = proposer.fork;
|
let fork = proposer.fork;
|
||||||
|
|
||||||
let pubkey_cache = chain.validator_pubkey_cache.read();
|
let pubkey_cache = ctx.validator_pubkey_cache.read();
|
||||||
let pubkey = pubkey_cache
|
let pubkey = pubkey_cache
|
||||||
.get(block.message().proposer_index() as usize)
|
.get(block.message().proposer_index() as usize)
|
||||||
.ok_or_else(|| EnvelopeError::UnknownValidator {
|
.ok_or_else(|| EnvelopeError::UnknownValidator {
|
||||||
@@ -149,16 +169,16 @@ impl<T: BeaconChainTypes> GossipVerifiedEnvelope<T> {
|
|||||||
let is_valid = signed_envelope.verify_signature(
|
let is_valid = signed_envelope.verify_signature(
|
||||||
pubkey,
|
pubkey,
|
||||||
&fork,
|
&fork,
|
||||||
chain.genesis_validators_root,
|
ctx.genesis_validators_root,
|
||||||
&chain.spec,
|
ctx.spec,
|
||||||
);
|
);
|
||||||
(is_valid, opt_snapshot)
|
(is_valid, opt_snapshot)
|
||||||
} else {
|
} else {
|
||||||
// TODO(gloas) if we implement a builder pubkey cache, we'll need to use it here.
|
// TODO(gloas) if we implement a builder pubkey cache, we'll need to use it here.
|
||||||
// External builder: must load the state to get the builder pubkey.
|
// External builder: must load the state to get the builder pubkey.
|
||||||
let snapshot = load_snapshot(signed_envelope.as_ref(), chain)?;
|
let snapshot = load_snapshot(signed_envelope.as_ref(), ctx.canonical_head, ctx.store)?;
|
||||||
let is_valid =
|
let is_valid =
|
||||||
signed_envelope.verify_signature_with_state(&snapshot.pre_state, &chain.spec)?;
|
signed_envelope.verify_signature_with_state(&snapshot.pre_state, ctx.spec)?;
|
||||||
(is_valid, Some(Box::new(snapshot)))
|
(is_valid, Some(Box::new(snapshot)))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -228,7 +248,11 @@ impl<T: BeaconChainTypes> IntoExecutionPendingEnvelope<T> for GossipVerifiedEnve
|
|||||||
let snapshot = if let Some(snapshot) = self.snapshot {
|
let snapshot = if let Some(snapshot) = self.snapshot {
|
||||||
*snapshot
|
*snapshot
|
||||||
} else {
|
} else {
|
||||||
load_snapshot(signed_envelope.as_ref(), chain)?
|
load_snapshot(
|
||||||
|
signed_envelope.as_ref(),
|
||||||
|
&chain.canonical_head,
|
||||||
|
&chain.store,
|
||||||
|
)?
|
||||||
};
|
};
|
||||||
let mut state = snapshot.pre_state;
|
let mut state = snapshot.pre_state;
|
||||||
|
|
||||||
@@ -263,6 +287,18 @@ impl<T: BeaconChainTypes> IntoExecutionPendingEnvelope<T> for GossipVerifiedEnve
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BeaconChainTypes> BeaconChain<T> {
|
impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||||
|
/// Build a `GossipVerificationContext` from this `BeaconChain`.
|
||||||
|
pub fn gossip_verification_context(&self) -> GossipVerificationContext<'_, T> {
|
||||||
|
GossipVerificationContext {
|
||||||
|
canonical_head: &self.canonical_head,
|
||||||
|
store: &self.store,
|
||||||
|
spec: &self.spec,
|
||||||
|
beacon_proposer_cache: &self.beacon_proposer_cache,
|
||||||
|
validator_pubkey_cache: &self.validator_pubkey_cache,
|
||||||
|
genesis_validators_root: self.genesis_validators_root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `Ok(GossipVerifiedEnvelope)` if the supplied `envelope` should be forwarded onto the
|
/// Returns `Ok(GossipVerifiedEnvelope)` if the supplied `envelope` should be forwarded onto the
|
||||||
/// gossip network. The envelope is not imported into the chain, it is just partially verified.
|
/// gossip network. The envelope is not imported into the chain, it is just partially verified.
|
||||||
///
|
///
|
||||||
@@ -287,7 +323,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let slot = envelope.slot();
|
let slot = envelope.slot();
|
||||||
let beacon_block_root = envelope.message.beacon_block_root;
|
let beacon_block_root = envelope.message.beacon_block_root;
|
||||||
|
|
||||||
match GossipVerifiedEnvelope::new(envelope, &chain) {
|
let ctx = chain.gossip_verification_context();
|
||||||
|
match GossipVerifiedEnvelope::new(envelope, &ctx) {
|
||||||
Ok(verified) => {
|
Ok(verified) => {
|
||||||
debug!(
|
debug!(
|
||||||
%slot,
|
%slot,
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ use types::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, ExecutionPayloadError,
|
BeaconChain, BeaconChainError, BeaconChainTypes, BeaconStore, BlockError,
|
||||||
NotifyExecutionLayer, PayloadVerificationOutcome,
|
ExecutionPayloadError, NotifyExecutionLayer, PayloadVerificationOutcome,
|
||||||
block_verification::PayloadVerificationHandle,
|
block_verification::PayloadVerificationHandle, canonical_head::CanonicalHead,
|
||||||
payload_envelope_verification::gossip_verified_envelope::GossipVerifiedEnvelope,
|
payload_envelope_verification::gossip_verified_envelope::GossipVerifiedEnvelope,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,6 +49,9 @@ pub mod gossip_verified_envelope;
|
|||||||
pub mod import;
|
pub mod import;
|
||||||
mod payload_notifier;
|
mod payload_notifier;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub trait IntoExecutionPendingEnvelope<T: BeaconChainTypes>: Sized {
|
pub trait IntoExecutionPendingEnvelope<T: BeaconChainTypes>: Sized {
|
||||||
fn into_execution_pending_envelope(
|
fn into_execution_pending_envelope(
|
||||||
self,
|
self,
|
||||||
@@ -289,7 +292,8 @@ impl From<EnvelopeProcessingError> for EnvelopeError {
|
|||||||
#[instrument(skip_all, level = "debug", fields(beacon_block_root = %envelope.beacon_block_root()))]
|
#[instrument(skip_all, level = "debug", fields(beacon_block_root = %envelope.beacon_block_root()))]
|
||||||
pub(crate) fn load_snapshot<T: BeaconChainTypes>(
|
pub(crate) fn load_snapshot<T: BeaconChainTypes>(
|
||||||
envelope: &SignedExecutionPayloadEnvelope<T::EthSpec>,
|
envelope: &SignedExecutionPayloadEnvelope<T::EthSpec>,
|
||||||
chain: &BeaconChain<T>,
|
canonical_head: &CanonicalHead<T>,
|
||||||
|
store: &BeaconStore<T>,
|
||||||
) -> Result<EnvelopeProcessingSnapshot<T::EthSpec>, EnvelopeError> {
|
) -> Result<EnvelopeProcessingSnapshot<T::EthSpec>, EnvelopeError> {
|
||||||
// Reject any envelope if its block is not known to fork choice.
|
// Reject any envelope if its block is not known to fork choice.
|
||||||
//
|
//
|
||||||
@@ -302,7 +306,7 @@ pub(crate) fn load_snapshot<T: BeaconChainTypes>(
|
|||||||
// choice, so we will not reject any child of the finalized block (this is relevant during
|
// choice, so we will not reject any child of the finalized block (this is relevant during
|
||||||
// genesis).
|
// genesis).
|
||||||
|
|
||||||
let fork_choice_read_lock = chain.canonical_head.fork_choice_read_lock();
|
let fork_choice_read_lock = canonical_head.fork_choice_read_lock();
|
||||||
let beacon_block_root = envelope.beacon_block_root();
|
let beacon_block_root = envelope.beacon_block_root();
|
||||||
let Some(proto_beacon_block) = fork_choice_read_lock.get_block(&beacon_block_root) else {
|
let Some(proto_beacon_block) = fork_choice_read_lock.get_block(&beacon_block_root) else {
|
||||||
return Err(EnvelopeError::BlockRootUnknown {
|
return Err(EnvelopeError::BlockRootUnknown {
|
||||||
@@ -317,8 +321,7 @@ pub(crate) fn load_snapshot<T: BeaconChainTypes>(
|
|||||||
// We can use `get_hot_state` here rather than `get_advanced_hot_state` because the envelope
|
// We can use `get_hot_state` here rather than `get_advanced_hot_state` because the envelope
|
||||||
// must be from the same slot as its block (so no advance is required).
|
// must be from the same slot as its block (so no advance is required).
|
||||||
let cache_state = true;
|
let cache_state = true;
|
||||||
let state = chain
|
let state = store
|
||||||
.store
|
|
||||||
.get_hot_state(&block_state_root, cache_state)
|
.get_hot_state(&block_state_root, cache_state)
|
||||||
.map_err(EnvelopeError::from)?
|
.map_err(EnvelopeError::from)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@@ -342,7 +345,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingEnvelope<T>
|
|||||||
chain: &Arc<BeaconChain<T>>,
|
chain: &Arc<BeaconChain<T>>,
|
||||||
notify_execution_layer: NotifyExecutionLayer,
|
notify_execution_layer: NotifyExecutionLayer,
|
||||||
) -> Result<ExecutionPendingEnvelope<T::EthSpec>, EnvelopeError> {
|
) -> Result<ExecutionPendingEnvelope<T::EthSpec>, EnvelopeError> {
|
||||||
GossipVerifiedEnvelope::new(self, chain)?
|
GossipVerifiedEnvelope::new(self, &chain.gossip_verification_context())?
|
||||||
.into_execution_pending_envelope(chain, notify_execution_layer)
|
.into_execution_pending_envelope(chain, notify_execution_layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,524 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bls::{FixedBytesExtended, Keypair, Signature};
|
||||||
|
use fork_choice::ForkChoice;
|
||||||
|
use parking_lot::{Mutex, RwLock};
|
||||||
|
use ssz_types::VariableList;
|
||||||
|
use store::{HotColdDB, KeyValueStore, MemoryStore, StoreConfig};
|
||||||
|
use types::consts::gloas::BUILDER_INDEX_SELF_BUILD;
|
||||||
|
use types::test_utils::generate_deterministic_keypairs;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
use crate::BeaconStore;
|
||||||
|
use crate::beacon_fork_choice_store::BeaconForkChoiceStore;
|
||||||
|
use crate::beacon_proposer_cache::BeaconProposerCache;
|
||||||
|
use crate::builder::Witness;
|
||||||
|
use crate::canonical_head::CanonicalHead;
|
||||||
|
use crate::payload_envelope_verification::EnvelopeError;
|
||||||
|
use crate::payload_envelope_verification::gossip_verified_envelope::{
|
||||||
|
GossipVerificationContext, GossipVerifiedEnvelope,
|
||||||
|
};
|
||||||
|
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
|
||||||
|
|
||||||
|
type TestEthSpec = MinimalEthSpec;
|
||||||
|
type TestTypes = Witness<
|
||||||
|
slot_clock::TestingSlotClock,
|
||||||
|
TestEthSpec,
|
||||||
|
MemoryStore<TestEthSpec>,
|
||||||
|
MemoryStore<TestEthSpec>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Test context that holds the minimal state needed for gossip verification.
|
||||||
|
struct TestContext {
|
||||||
|
store: BeaconStore<TestTypes>,
|
||||||
|
canonical_head: CanonicalHead<TestTypes>,
|
||||||
|
beacon_proposer_cache: Mutex<BeaconProposerCache>,
|
||||||
|
validator_pubkey_cache: RwLock<ValidatorPubkeyCache<TestTypes>>,
|
||||||
|
spec: Arc<ChainSpec>,
|
||||||
|
keypairs: Vec<Keypair>,
|
||||||
|
genesis_state: BeaconState<TestEthSpec>,
|
||||||
|
genesis_block_root: Hash256,
|
||||||
|
genesis_validators_root: Hash256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestContext {
|
||||||
|
fn new(validator_count: usize) -> Self {
|
||||||
|
let spec = Arc::new(ForkName::Gloas.make_genesis_spec(ChainSpec::minimal()));
|
||||||
|
let keypairs = generate_deterministic_keypairs(validator_count);
|
||||||
|
|
||||||
|
let mut genesis_state = genesis::interop_genesis_state::<TestEthSpec>(
|
||||||
|
&keypairs,
|
||||||
|
0, // genesis_time
|
||||||
|
Hash256::from_slice(&[0x42; 32]),
|
||||||
|
None, // no execution payload header
|
||||||
|
&spec,
|
||||||
|
)
|
||||||
|
.expect("should create genesis state");
|
||||||
|
|
||||||
|
let genesis_validators_root = genesis_state.genesis_validators_root();
|
||||||
|
|
||||||
|
let store: BeaconStore<TestTypes> = Arc::new(
|
||||||
|
HotColdDB::open_ephemeral(StoreConfig::default(), spec.clone())
|
||||||
|
.expect("should create ephemeral store"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize store metadata.
|
||||||
|
let genesis_block = BeaconBlock::<TestEthSpec>::empty(&spec);
|
||||||
|
let genesis_block_root = genesis_block.canonical_root();
|
||||||
|
let signed_genesis_block = SignedBeaconBlock::from_block(genesis_block, Signature::empty());
|
||||||
|
|
||||||
|
// Build caches and compute state root before storing.
|
||||||
|
genesis_state
|
||||||
|
.build_caches(&spec)
|
||||||
|
.expect("should build caches");
|
||||||
|
|
||||||
|
// Initialize store metadata ops (must be done before put_state).
|
||||||
|
let ops = vec![
|
||||||
|
store
|
||||||
|
.init_anchor_info(
|
||||||
|
signed_genesis_block.parent_root(),
|
||||||
|
signed_genesis_block.slot(),
|
||||||
|
Slot::new(0),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.expect("should init anchor info"),
|
||||||
|
store
|
||||||
|
.init_blob_info(signed_genesis_block.slot())
|
||||||
|
.expect("should init blob info"),
|
||||||
|
store
|
||||||
|
.init_data_column_info(signed_genesis_block.slot())
|
||||||
|
.expect("should init data column info"),
|
||||||
|
];
|
||||||
|
store
|
||||||
|
.hot_db
|
||||||
|
.do_atomically(ops)
|
||||||
|
.expect("should store metadata");
|
||||||
|
|
||||||
|
// Store the genesis block and state.
|
||||||
|
store
|
||||||
|
.put_block(&genesis_block_root, signed_genesis_block.clone())
|
||||||
|
.expect("should store genesis block");
|
||||||
|
let state_root = genesis_state
|
||||||
|
.update_tree_hash_cache()
|
||||||
|
.expect("should compute state root");
|
||||||
|
store
|
||||||
|
.put_state(&state_root, &genesis_state)
|
||||||
|
.expect("should store genesis state");
|
||||||
|
|
||||||
|
// Create BeaconSnapshot and fork choice.
|
||||||
|
let snapshot = crate::BeaconSnapshot {
|
||||||
|
beacon_block: Arc::new(signed_genesis_block),
|
||||||
|
beacon_block_root: genesis_block_root,
|
||||||
|
beacon_state: genesis_state.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store.clone(), snapshot.clone())
|
||||||
|
.expect("should create fork choice store");
|
||||||
|
|
||||||
|
let fork_choice = ForkChoice::from_anchor(
|
||||||
|
fc_store,
|
||||||
|
genesis_block_root,
|
||||||
|
&snapshot.beacon_block,
|
||||||
|
&snapshot.beacon_state,
|
||||||
|
None,
|
||||||
|
&spec,
|
||||||
|
)
|
||||||
|
.expect("should create fork choice from anchor");
|
||||||
|
|
||||||
|
let canonical_head = CanonicalHead::new(fork_choice, Arc::new(snapshot));
|
||||||
|
|
||||||
|
let validator_pubkey_cache = ValidatorPubkeyCache::new(&genesis_state, store.clone())
|
||||||
|
.expect("should create validator pubkey cache");
|
||||||
|
|
||||||
|
TestContext {
|
||||||
|
store,
|
||||||
|
canonical_head,
|
||||||
|
beacon_proposer_cache: Mutex::new(BeaconProposerCache::default()),
|
||||||
|
validator_pubkey_cache: RwLock::new(validator_pubkey_cache),
|
||||||
|
spec,
|
||||||
|
keypairs,
|
||||||
|
genesis_state,
|
||||||
|
genesis_block_root,
|
||||||
|
genesis_validators_root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gossip_verification_context(&self) -> GossipVerificationContext<'_, TestTypes> {
|
||||||
|
GossipVerificationContext {
|
||||||
|
canonical_head: &self.canonical_head,
|
||||||
|
store: &self.store,
|
||||||
|
spec: &self.spec,
|
||||||
|
beacon_proposer_cache: &self.beacon_proposer_cache,
|
||||||
|
validator_pubkey_cache: &self.validator_pubkey_cache,
|
||||||
|
genesis_validators_root: self.genesis_validators_root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a gloas block at `slot` with the given proposer, store it, add it to fork choice,
|
||||||
|
/// and return the signed block, block root, and post-state.
|
||||||
|
fn build_and_import_block(
|
||||||
|
&self,
|
||||||
|
slot: Slot,
|
||||||
|
proposer_index: usize,
|
||||||
|
execution_bid: ExecutionPayloadBid<TestEthSpec>,
|
||||||
|
) -> (
|
||||||
|
Arc<SignedBeaconBlock<TestEthSpec>>,
|
||||||
|
Hash256,
|
||||||
|
BeaconState<TestEthSpec>,
|
||||||
|
) {
|
||||||
|
let mut state = self.genesis_state.clone();
|
||||||
|
|
||||||
|
// Advance the state to the target slot.
|
||||||
|
if slot > state.slot() {
|
||||||
|
state_processing::state_advance::complete_state_advance(
|
||||||
|
&mut state, None, slot, &self.spec,
|
||||||
|
)
|
||||||
|
.expect("should advance state");
|
||||||
|
}
|
||||||
|
|
||||||
|
state.build_caches(&self.spec).expect("should build caches");
|
||||||
|
|
||||||
|
// Compute the state root so we can embed it in the block.
|
||||||
|
let state_root = state
|
||||||
|
.update_tree_hash_cache()
|
||||||
|
.expect("should compute state root");
|
||||||
|
|
||||||
|
let signed_bid = SignedExecutionPayloadBid {
|
||||||
|
message: execution_bid,
|
||||||
|
signature: Signature::infinity().expect("should create infinity signature"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the block body with the actual state root.
|
||||||
|
let block = BeaconBlock::Gloas(BeaconBlockGloas {
|
||||||
|
slot,
|
||||||
|
proposer_index: proposer_index as u64,
|
||||||
|
parent_root: self.genesis_block_root,
|
||||||
|
state_root,
|
||||||
|
body: BeaconBlockBodyGloas {
|
||||||
|
randao_reveal: Signature::empty(),
|
||||||
|
eth1_data: state.eth1_data().clone(),
|
||||||
|
graffiti: Graffiti::default(),
|
||||||
|
proposer_slashings: VariableList::empty(),
|
||||||
|
attester_slashings: VariableList::empty(),
|
||||||
|
attestations: VariableList::empty(),
|
||||||
|
deposits: VariableList::empty(),
|
||||||
|
voluntary_exits: VariableList::empty(),
|
||||||
|
sync_aggregate: SyncAggregate::empty(),
|
||||||
|
bls_to_execution_changes: VariableList::empty(),
|
||||||
|
signed_execution_payload_bid: signed_bid,
|
||||||
|
payload_attestations: VariableList::empty(),
|
||||||
|
_phantom: std::marker::PhantomData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let block_root = block.canonical_root();
|
||||||
|
let proposer_sk = &self.keypairs[proposer_index].sk;
|
||||||
|
let fork = self
|
||||||
|
.spec
|
||||||
|
.fork_at_epoch(slot.epoch(TestEthSpec::slots_per_epoch()));
|
||||||
|
let signed_block = block.sign(proposer_sk, &fork, self.genesis_validators_root, &self.spec);
|
||||||
|
|
||||||
|
// Store block and state.
|
||||||
|
self.store
|
||||||
|
.put_block(&block_root, signed_block.clone())
|
||||||
|
.expect("should store block");
|
||||||
|
self.store
|
||||||
|
.put_state(&state_root, &state)
|
||||||
|
.expect("should store state");
|
||||||
|
|
||||||
|
// Add block to fork choice.
|
||||||
|
let mut fork_choice = self.canonical_head.fork_choice_write_lock();
|
||||||
|
fork_choice
|
||||||
|
.on_block(
|
||||||
|
slot,
|
||||||
|
signed_block.message(),
|
||||||
|
block_root,
|
||||||
|
Duration::from_secs(0),
|
||||||
|
&state,
|
||||||
|
crate::PayloadVerificationStatus::Verified,
|
||||||
|
&self.spec,
|
||||||
|
)
|
||||||
|
.expect("should add block to fork choice");
|
||||||
|
drop(fork_choice);
|
||||||
|
|
||||||
|
(Arc::new(signed_block), block_root, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a signed execution payload envelope for the given block.
|
||||||
|
fn build_signed_envelope(
|
||||||
|
&self,
|
||||||
|
block_root: Hash256,
|
||||||
|
slot: Slot,
|
||||||
|
builder_index: u64,
|
||||||
|
block_hash: ExecutionBlockHash,
|
||||||
|
signing_key: &bls::SecretKey,
|
||||||
|
) -> Arc<SignedExecutionPayloadEnvelope<TestEthSpec>> {
|
||||||
|
let mut payload = ExecutionPayloadGloas::default();
|
||||||
|
payload.block_hash = block_hash;
|
||||||
|
|
||||||
|
let envelope = ExecutionPayloadEnvelope {
|
||||||
|
payload,
|
||||||
|
execution_requests: ExecutionRequests::default(),
|
||||||
|
builder_index,
|
||||||
|
beacon_block_root: block_root,
|
||||||
|
slot,
|
||||||
|
state_root: Hash256::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let fork = self
|
||||||
|
.spec
|
||||||
|
.fork_at_epoch(slot.epoch(TestEthSpec::slots_per_epoch()));
|
||||||
|
let domain = self.spec.get_domain(
|
||||||
|
slot.epoch(TestEthSpec::slots_per_epoch()),
|
||||||
|
Domain::BeaconBuilder,
|
||||||
|
&fork,
|
||||||
|
self.genesis_validators_root,
|
||||||
|
);
|
||||||
|
let message = envelope.signing_root(domain);
|
||||||
|
let signature = signing_key.sign(message);
|
||||||
|
|
||||||
|
Arc::new(SignedExecutionPayloadEnvelope {
|
||||||
|
message: envelope,
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper: build a block and matching self-build envelope.
|
||||||
|
fn build_block_and_envelope(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
Arc<SignedBeaconBlock<TestEthSpec>>,
|
||||||
|
Hash256,
|
||||||
|
Arc<SignedExecutionPayloadEnvelope<TestEthSpec>>,
|
||||||
|
) {
|
||||||
|
let slot = Slot::new(1);
|
||||||
|
let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32]));
|
||||||
|
|
||||||
|
// Get proposer for slot 1.
|
||||||
|
let mut state = self.genesis_state.clone();
|
||||||
|
state_processing::state_advance::complete_state_advance(&mut state, None, slot, &self.spec)
|
||||||
|
.expect("should advance state");
|
||||||
|
state.build_caches(&self.spec).expect("should build caches");
|
||||||
|
let proposer_index = state
|
||||||
|
.get_beacon_proposer_index(slot, &self.spec)
|
||||||
|
.expect("should get proposer index");
|
||||||
|
|
||||||
|
let bid = ExecutionPayloadBid {
|
||||||
|
builder_index: BUILDER_INDEX_SELF_BUILD,
|
||||||
|
block_hash,
|
||||||
|
slot,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (signed_block, block_root, _post_state) =
|
||||||
|
self.build_and_import_block(slot, proposer_index, bid);
|
||||||
|
|
||||||
|
let proposer_sk = &self.keypairs[proposer_index].sk;
|
||||||
|
let envelope = self.build_signed_envelope(
|
||||||
|
block_root,
|
||||||
|
slot,
|
||||||
|
BUILDER_INDEX_SELF_BUILD,
|
||||||
|
block_hash,
|
||||||
|
proposer_sk,
|
||||||
|
);
|
||||||
|
|
||||||
|
(signed_block, block_root, envelope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_self_build_envelope() {
|
||||||
|
let ctx = TestContext::new(32);
|
||||||
|
let (_block, _block_root, envelope) = ctx.build_block_and_envelope();
|
||||||
|
let gossip_ctx = ctx.gossip_verification_context();
|
||||||
|
|
||||||
|
let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx);
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"valid self-build envelope should pass verification, got: {:?}",
|
||||||
|
result.err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unknown_block_root() {
|
||||||
|
let ctx = TestContext::new(32);
|
||||||
|
let gossip_ctx = ctx.gossip_verification_context();
|
||||||
|
|
||||||
|
// Build an envelope referencing a block root not in fork choice.
|
||||||
|
let unknown_root = Hash256::from_slice(&[0xff; 32]);
|
||||||
|
let envelope = ctx.build_signed_envelope(
|
||||||
|
unknown_root,
|
||||||
|
Slot::new(1),
|
||||||
|
BUILDER_INDEX_SELF_BUILD,
|
||||||
|
ExecutionBlockHash::from_root(Hash256::zero()),
|
||||||
|
&ctx.keypairs[0].sk,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx);
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(EnvelopeError::BlockRootUnknown { .. })),
|
||||||
|
"should reject envelope with unknown block root, got: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slot_mismatch() {
|
||||||
|
let ctx = TestContext::new(32);
|
||||||
|
let (_block, block_root, _good_envelope) = ctx.build_block_and_envelope();
|
||||||
|
let gossip_ctx = ctx.gossip_verification_context();
|
||||||
|
|
||||||
|
// Build an envelope with a different slot than the block.
|
||||||
|
let wrong_slot = Slot::new(2);
|
||||||
|
let envelope = ctx.build_signed_envelope(
|
||||||
|
block_root,
|
||||||
|
wrong_slot,
|
||||||
|
BUILDER_INDEX_SELF_BUILD,
|
||||||
|
ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32])),
|
||||||
|
&ctx.keypairs[0].sk,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx);
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(EnvelopeError::SlotMismatch { .. })),
|
||||||
|
"should reject envelope with slot mismatch, got: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builder_index_mismatch() {
|
||||||
|
let ctx = TestContext::new(32);
|
||||||
|
let gossip_ctx = ctx.gossip_verification_context();
|
||||||
|
|
||||||
|
let slot = Slot::new(1);
|
||||||
|
let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32]));
|
||||||
|
|
||||||
|
// Get proposer for slot 1.
|
||||||
|
let mut state = ctx.genesis_state.clone();
|
||||||
|
state_processing::state_advance::complete_state_advance(&mut state, None, slot, &ctx.spec)
|
||||||
|
.expect("should advance state");
|
||||||
|
state.build_caches(&ctx.spec).expect("should build caches");
|
||||||
|
let proposer_index = state
|
||||||
|
.get_beacon_proposer_index(slot, &ctx.spec)
|
||||||
|
.expect("should get proposer index");
|
||||||
|
|
||||||
|
// Block has builder_index = BUILDER_INDEX_SELF_BUILD
|
||||||
|
let bid = ExecutionPayloadBid {
|
||||||
|
builder_index: BUILDER_INDEX_SELF_BUILD,
|
||||||
|
block_hash,
|
||||||
|
slot,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let (_block, block_root, _post_state) = ctx.build_and_import_block(slot, proposer_index, bid);
|
||||||
|
|
||||||
|
// Envelope has a different builder_index.
|
||||||
|
let wrong_builder_index = 999;
|
||||||
|
let envelope = ctx.build_signed_envelope(
|
||||||
|
block_root,
|
||||||
|
slot,
|
||||||
|
wrong_builder_index,
|
||||||
|
block_hash,
|
||||||
|
&ctx.keypairs[proposer_index].sk,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx);
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(EnvelopeError::BuilderIndexMismatch { .. })),
|
||||||
|
"should reject envelope with builder index mismatch, got: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_block_hash_mismatch() {
|
||||||
|
let ctx = TestContext::new(32);
|
||||||
|
let gossip_ctx = ctx.gossip_verification_context();
|
||||||
|
|
||||||
|
let slot = Slot::new(1);
|
||||||
|
let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32]));
|
||||||
|
let wrong_block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xbb; 32]));
|
||||||
|
|
||||||
|
// Get proposer for slot 1.
|
||||||
|
let mut state = ctx.genesis_state.clone();
|
||||||
|
state_processing::state_advance::complete_state_advance(&mut state, None, slot, &ctx.spec)
|
||||||
|
.expect("should advance state");
|
||||||
|
state.build_caches(&ctx.spec).expect("should build caches");
|
||||||
|
let proposer_index = state
|
||||||
|
.get_beacon_proposer_index(slot, &ctx.spec)
|
||||||
|
.expect("should get proposer index");
|
||||||
|
|
||||||
|
// Block has block_hash = 0xaa
|
||||||
|
let bid = ExecutionPayloadBid {
|
||||||
|
builder_index: BUILDER_INDEX_SELF_BUILD,
|
||||||
|
block_hash,
|
||||||
|
slot,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let (_block, block_root, _post_state) = ctx.build_and_import_block(slot, proposer_index, bid);
|
||||||
|
|
||||||
|
// Envelope has a different block_hash.
|
||||||
|
let envelope = ctx.build_signed_envelope(
|
||||||
|
block_root,
|
||||||
|
slot,
|
||||||
|
BUILDER_INDEX_SELF_BUILD,
|
||||||
|
wrong_block_hash,
|
||||||
|
&ctx.keypairs[proposer_index].sk,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx);
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(EnvelopeError::BlockHashMismatch { .. })),
|
||||||
|
"should reject envelope with block hash mismatch, got: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_signature() {
|
||||||
|
let ctx = TestContext::new(32);
|
||||||
|
let gossip_ctx = ctx.gossip_verification_context();
|
||||||
|
|
||||||
|
let slot = Slot::new(1);
|
||||||
|
let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32]));
|
||||||
|
|
||||||
|
// Get proposer for slot 1.
|
||||||
|
let mut state = ctx.genesis_state.clone();
|
||||||
|
state_processing::state_advance::complete_state_advance(&mut state, None, slot, &ctx.spec)
|
||||||
|
.expect("should advance state");
|
||||||
|
state.build_caches(&ctx.spec).expect("should build caches");
|
||||||
|
let proposer_index = state
|
||||||
|
.get_beacon_proposer_index(slot, &ctx.spec)
|
||||||
|
.expect("should get proposer index");
|
||||||
|
|
||||||
|
let bid = ExecutionPayloadBid {
|
||||||
|
builder_index: BUILDER_INDEX_SELF_BUILD,
|
||||||
|
block_hash,
|
||||||
|
slot,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let (_block, block_root, _post_state) = ctx.build_and_import_block(slot, proposer_index, bid);
|
||||||
|
|
||||||
|
// Sign the envelope with the wrong key (some other validator's key).
|
||||||
|
let wrong_key_index = if proposer_index == 0 { 1 } else { 0 };
|
||||||
|
let envelope = ctx.build_signed_envelope(
|
||||||
|
block_root,
|
||||||
|
slot,
|
||||||
|
BUILDER_INDEX_SELF_BUILD,
|
||||||
|
block_hash,
|
||||||
|
&ctx.keypairs[wrong_key_index].sk,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx);
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(EnvelopeError::BadSignature)),
|
||||||
|
"should reject envelope with bad signature, got: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: `test_prior_to_finalization` is omitted here because advancing finalization requires
|
||||||
|
// attestation-based justification which needs the full `BeaconChainHarness`. The
|
||||||
|
// `PriorToFinalization` code path is tested in the integration tests.
|
||||||
Reference in New Issue
Block a user