mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 12:47:05 +00:00
Use async code when interacting with EL (#3244)
## Overview
This rather extensive PR achieves two primary goals:
1. Uses the finalized/justified checkpoints of fork choice (FC), rather than that of the head state.
2. Refactors fork choice, block production and block processing to `async` functions.
Additionally, it achieves:
- Concurrent forkchoice updates to the EL and cache pruning after a new head is selected.
- Concurrent "block packing" (attestations, etc) and execution payload retrieval during block production.
- Concurrent per-block-processing and execution payload verification during block processing.
- The `Arc`-ification of `SignedBeaconBlock` during block processing (it's never mutated, so why not?):
- I had to do this to deal with sending blocks into spawned tasks.
- Previously we were cloning the beacon block at least 2 times during each block processing, these clones are either removed or turned into cheaper `Arc` clones.
- We were also `Box`-ing and un-`Box`-ing beacon blocks as they moved throughout the networking crate. This is not a big deal, but it's nice to avoid shifting things between the stack and heap.
- Avoids cloning *all the blocks* in *every chain segment* during sync.
- It also has the potential to clean up our code where we need to pass an *owned* block around so we can send it back in the case of an error (I didn't do much of this, my PR is already big enough 😅)
- The `BeaconChain::HeadSafetyStatus` struct was removed. It was an old relic from prior merge specs.
For motivation for this change, see https://github.com/sigp/lighthouse/pull/3244#issuecomment-1160963273
## Changes to `canonical_head` and `fork_choice`
Previously, the `BeaconChain` had two separate fields:
```
canonical_head: RwLock<Snapshot>,
fork_choice: RwLock<BeaconForkChoice>
```
Now, we have grouped these values under a single struct:
```
canonical_head: CanonicalHead {
cached_head: RwLock<Arc<Snapshot>>,
fork_choice: RwLock<BeaconForkChoice>
}
```
Apart from ergonomics, the only *actual* change here is wrapping the canonical head snapshot in an `Arc`. This means that we no longer need to hold the `cached_head` (`canonical_head`, in old terms) lock when we want to pull some values from it. This was done to avoid deadlock risks by preventing functions from acquiring (and holding) the `cached_head` and `fork_choice` locks simultaneously.
## Breaking Changes
### The `state` (root) field in the `finalized_checkpoint` SSE event
Consider the scenario where epoch `n` is just finalized, but `start_slot(n)` is skipped. There are two state roots we might in the `finalized_checkpoint` SSE event:
1. The state root of the finalized block, which is `get_block(finalized_checkpoint.root).state_root`.
4. The state root at slot of `start_slot(n)`, which would be the state from (1), but "skipped forward" through any skip slots.
Previously, Lighthouse would choose (2). However, we can see that when [Teku generates that event](de2b2801c8/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java (L171-L182)) it uses [`getStateRootFromBlockRoot`](de2b2801c8/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java (L336-L341)) which uses (1).
I have switched Lighthouse from (2) to (1). I think it's a somewhat arbitrary choice between the two, where (1) is easier to compute and is consistent with Teku.
## Notes for Reviewers
I've renamed `BeaconChain::fork_choice` to `BeaconChain::recompute_head`. Doing this helped ensure I broke all previous uses of fork choice and I also find it more descriptive. It describes an action and can't be confused with trying to get a reference to the `ForkChoice` struct.
I've changed the ordering of SSE events when a block is received. It used to be `[block, finalized, head]` and now it's `[block, head, finalized]`. It was easier this way and I don't think we were making any promises about SSE event ordering so it's not "breaking".
I've made it so fork choice will run when it's first constructed. I did this because I wanted to have a cached version of the last call to `get_head`. Ensuring `get_head` has been run *at least once* means that the cached values doesn't need to wrapped in an `Option`. This was fairly simple, it just involved passing a `slot` to the constructor so it knows *when* it's being run. When loading a fork choice from the store and a slot clock isn't handy I've just used the `slot` that was saved in the `fork_choice_store`. That seems like it would be a faithful representation of the slot when we saved it.
I added the `genesis_time: u64` to the `BeaconChain`. It's small, constant and nice to have around.
Since we're using FC for the fin/just checkpoints, we no longer get the `0x00..00` roots at genesis. You can see I had to remove a work-around in `ef-tests` here: b56be3bc2. I can't find any reason why this would be an issue, if anything I think it'll be better since the genesis-alias has caught us out a few times (0x00..00 isn't actually a real root). Edit: I did find a case where the `network` expected the 0x00..00 alias and patched it here: 3f26ac3e2.
You'll notice a lot of changes in tests. Generally, tests should be functionally equivalent. Here are the things creating the most diff-noise in tests:
- Changing tests to be `tokio::async` tests.
- Adding `.await` to fork choice, block processing and block production functions.
- Refactor of the `canonical_head` "API" provided by the `BeaconChain`. E.g., `chain.canonical_head.cached_head()` instead of `chain.canonical_head.read()`.
- Wrapping `SignedBeaconBlock` in an `Arc`.
- In the `beacon_chain/tests/block_verification`, we can't use the `lazy_static` `CHAIN_SEGMENT` variable anymore since it's generated with an async function. We just generate it in each test, not so efficient but hopefully insignificant.
I had to disable `rayon` concurrent tests in the `fork_choice` tests. This is because the use of `rayon` and `block_on` was causing a panic.
Co-authored-by: Mac L <mjladson@pm.me>
This commit is contained in:
@@ -976,8 +976,8 @@ fn verify_head_block_is_known<T: BeaconChainTypes>(
|
||||
max_skip_slots: Option<u64>,
|
||||
) -> Result<ProtoBlock, Error> {
|
||||
let block_opt = chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&attestation.data.beacon_block_root)
|
||||
.or_else(|| {
|
||||
chain
|
||||
@@ -1245,7 +1245,10 @@ where
|
||||
// processing an attestation that does not include our latest finalized block in its chain.
|
||||
//
|
||||
// We do not delay consideration for later, we simply drop the attestation.
|
||||
if !chain.fork_choice.read().contains_block(&target.root)
|
||||
if !chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.contains_block(&target.root)
|
||||
&& !chain.early_attester_cache.contains_block(target.root)
|
||||
{
|
||||
return Err(Error::UnknownTargetRoot(target.root));
|
||||
|
||||
@@ -65,7 +65,7 @@ where
|
||||
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
|
||||
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
|
||||
|
||||
let fork = chain.with_head(|head| Ok::<_, BeaconChainError>(head.beacon_state.fork()))?;
|
||||
let fork = chain.canonical_head.cached_head().head_fork();
|
||||
|
||||
let mut signature_sets = Vec::with_capacity(num_indexed * 3);
|
||||
|
||||
@@ -169,13 +169,13 @@ where
|
||||
&metrics::ATTESTATION_PROCESSING_BATCH_UNAGG_SIGNATURE_SETUP_TIMES,
|
||||
);
|
||||
|
||||
let fork = chain.canonical_head.cached_head().head_fork();
|
||||
|
||||
let pubkey_cache = chain
|
||||
.validator_pubkey_cache
|
||||
.try_read_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
|
||||
.ok_or(BeaconChainError::ValidatorPubkeyCacheLockTimeout)?;
|
||||
|
||||
let fork = chain.with_head(|head| Ok::<_, BeaconChainError>(head.beacon_state.fork()))?;
|
||||
|
||||
let mut signature_sets = Vec::with_capacity(num_partially_verified);
|
||||
|
||||
// Iterate, flattening to get only the `Ok` values.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,8 @@ use std::sync::Arc;
|
||||
use store::{Error as StoreError, HotColdDB, ItemStore};
|
||||
use superstruct::superstruct;
|
||||
use types::{
|
||||
BeaconBlock, BeaconState, BeaconStateError, Checkpoint, Epoch, EthSpec, ExecPayload, Hash256,
|
||||
Slot,
|
||||
BeaconBlockRef, BeaconState, BeaconStateError, Checkpoint, Epoch, EthSpec, ExecPayload,
|
||||
Hash256, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -257,7 +257,7 @@ where
|
||||
|
||||
fn on_verified_block<Payload: ExecPayload<E>>(
|
||||
&mut self,
|
||||
_block: &BeaconBlock<E, Payload>,
|
||||
_block: BeaconBlockRef<E, Payload>,
|
||||
block_root: Hash256,
|
||||
state: &BeaconState<E>,
|
||||
) -> Result<(), Self::Error> {
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
//! values it stores are very small, so this should not be an issue.
|
||||
|
||||
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
||||
use fork_choice::ExecutionStatus;
|
||||
use lru::LruCache;
|
||||
use smallvec::SmallVec;
|
||||
use state_processing::state_advance::partial_state_advance;
|
||||
use std::cmp::Ordering;
|
||||
use types::{
|
||||
BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, Fork, Hash256, Slot, Unsigned,
|
||||
BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, Fork, Hash256, Slot,
|
||||
Unsigned,
|
||||
};
|
||||
|
||||
/// The number of sets of proposer indices that should be cached.
|
||||
@@ -135,11 +137,26 @@ impl BeaconProposerCache {
|
||||
pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
|
||||
current_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<(Vec<usize>, Hash256, Fork), BeaconChainError> {
|
||||
// Take a copy of the head of the chain.
|
||||
let head = chain.head()?;
|
||||
let mut state = head.beacon_state;
|
||||
let head_state_root = head.beacon_block.state_root();
|
||||
) -> Result<(Vec<usize>, Hash256, ExecutionStatus, Fork), BeaconChainError> {
|
||||
// Atomically collect information about the head whilst holding the canonical head `Arc` as
|
||||
// short as possible.
|
||||
let (mut state, head_state_root, head_block_root) = {
|
||||
let head = chain.canonical_head.cached_head();
|
||||
// Take a copy of the head state.
|
||||
let head_state = head
|
||||
.snapshot
|
||||
.beacon_state
|
||||
.clone_with(CloneConfig::committee_caches_only());
|
||||
let head_state_root = head.head_state_root();
|
||||
let head_block_root = head.head_block_root();
|
||||
(head_state, head_state_root, head_block_root)
|
||||
};
|
||||
|
||||
let execution_status = chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block_execution_status(&head_block_root)
|
||||
.ok_or(BeaconChainError::HeadMissingFromForkChoice(head_block_root))?;
|
||||
|
||||
// Advance the state into the requested epoch.
|
||||
ensure_state_is_in_epoch(&mut state, head_state_root, current_epoch, &chain.spec)?;
|
||||
@@ -153,7 +170,7 @@ pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
|
||||
.proposer_shuffling_decision_root(chain.genesis_block_root)
|
||||
.map_err(BeaconChainError::from)?;
|
||||
|
||||
Ok((indices, dependent_root, state.fork()))
|
||||
Ok((indices, dependent_root, execution_status, state.fork()))
|
||||
}
|
||||
|
||||
/// If required, advance `state` to `target_epoch`.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde_derive::Serialize;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
beacon_state::CloneConfig, BeaconState, EthSpec, ExecPayload, FullPayload, Hash256,
|
||||
SignedBeaconBlock,
|
||||
@@ -8,7 +9,7 @@ use types::{
|
||||
/// head, justified head and finalized head.
|
||||
#[derive(Clone, Serialize, PartialEq, Debug)]
|
||||
pub struct BeaconSnapshot<E: EthSpec, Payload: ExecPayload<E> = FullPayload<E>> {
|
||||
pub beacon_block: SignedBeaconBlock<E, Payload>,
|
||||
pub beacon_block: Arc<SignedBeaconBlock<E, Payload>>,
|
||||
pub beacon_block_root: Hash256,
|
||||
pub beacon_state: BeaconState<E>,
|
||||
}
|
||||
@@ -16,7 +17,7 @@ pub struct BeaconSnapshot<E: EthSpec, Payload: ExecPayload<E> = FullPayload<E>>
|
||||
impl<E: EthSpec, Payload: ExecPayload<E>> BeaconSnapshot<E, Payload> {
|
||||
/// Create a new checkpoint.
|
||||
pub fn new(
|
||||
beacon_block: SignedBeaconBlock<E, Payload>,
|
||||
beacon_block: Arc<SignedBeaconBlock<E, Payload>>,
|
||||
beacon_block_root: Hash256,
|
||||
beacon_state: BeaconState<E>,
|
||||
) -> Self {
|
||||
@@ -39,7 +40,7 @@ impl<E: EthSpec, Payload: ExecPayload<E>> BeaconSnapshot<E, Payload> {
|
||||
/// Update all fields of the checkpoint.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
beacon_block: SignedBeaconBlock<E, Payload>,
|
||||
beacon_block: Arc<SignedBeaconBlock<E, Payload>>,
|
||||
beacon_block_root: Hash256,
|
||||
beacon_state: BeaconState<E>,
|
||||
) {
|
||||
|
||||
@@ -31,24 +31,27 @@
|
||||
//! |---------------
|
||||
//! |
|
||||
//! ▼
|
||||
//! SignatureVerifiedBlock
|
||||
//! SignatureVerifiedBlock
|
||||
//! |
|
||||
//! ▼
|
||||
//! FullyVerifiedBlock
|
||||
//! ExecutionPendingBlock
|
||||
//! |
|
||||
//! await
|
||||
//! |
|
||||
//! ▼
|
||||
//! END
|
||||
//!
|
||||
//! ```
|
||||
use crate::execution_payload::{
|
||||
notify_new_payload, validate_execution_payload_for_gossip, validate_merge_block,
|
||||
is_optimistic_candidate_block, validate_execution_payload_for_gossip, validate_merge_block,
|
||||
PayloadNotifier,
|
||||
};
|
||||
use crate::snapshot_cache::PreProcessingSnapshot;
|
||||
use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS;
|
||||
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
|
||||
use crate::{
|
||||
beacon_chain::{
|
||||
BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
BeaconForkChoice, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
|
||||
},
|
||||
metrics, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
@@ -56,11 +59,11 @@ use crate::{
|
||||
use derivative::Derivative;
|
||||
use eth2::types::EventKind;
|
||||
use execution_layer::PayloadStatus;
|
||||
use fork_choice::{ForkChoice, ForkChoiceStore, PayloadVerificationStatus};
|
||||
use fork_choice::PayloadVerificationStatus;
|
||||
use parking_lot::RwLockReadGuard;
|
||||
use proto_array::Block as ProtoBlock;
|
||||
use safe_arith::ArithError;
|
||||
use slog::{debug, error, info, Logger};
|
||||
use slog::{debug, error, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::Encode;
|
||||
use state_processing::per_block_processing::is_merge_transition_block;
|
||||
@@ -75,16 +78,16 @@ use std::fs;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use store::{Error as DBError, HotColdDB, HotStateSummary, KeyValueStore, StoreOp};
|
||||
use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp};
|
||||
use task_executor::JoinHandle;
|
||||
use tree_hash::TreeHash;
|
||||
use types::ExecPayload;
|
||||
use types::{
|
||||
BeaconBlockRef, BeaconState, BeaconStateError, BlindedPayload, ChainSpec, CloneConfig, Epoch,
|
||||
EthSpec, ExecutionBlockHash, Hash256, InconsistentFork, PublicKey, PublicKeyBytes,
|
||||
RelativeEpoch, SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
|
||||
};
|
||||
|
||||
const POS_PANDA_BANNER: &str = r#"
|
||||
pub const POS_PANDA_BANNER: &str = r#"
|
||||
,,, ,,, ,,, ,,,
|
||||
;" ^; ;' ", ;" ^; ;' ",
|
||||
; s$$$$$$$s ; ; s$$$$$$$s ;
|
||||
@@ -129,7 +132,7 @@ pub enum BlockError<T: EthSpec> {
|
||||
///
|
||||
/// It's unclear if this block is valid, but it cannot be processed without already knowing
|
||||
/// its parent.
|
||||
ParentUnknown(Box<SignedBeaconBlock<T>>),
|
||||
ParentUnknown(Arc<SignedBeaconBlock<T>>),
|
||||
/// The block skips too many slots and is a DoS risk.
|
||||
TooManySkippedSlots { parent_slot: Slot, block_slot: Slot },
|
||||
/// The block slot is greater than the present slot.
|
||||
@@ -419,6 +422,12 @@ impl<T: EthSpec> From<ArithError> for BlockError<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores information about verifying a payload against an execution engine.
|
||||
pub struct PayloadVerificationOutcome {
|
||||
pub payload_verification_status: PayloadVerificationStatus,
|
||||
pub is_valid_merge_transition_block: bool,
|
||||
}
|
||||
|
||||
/// Information about invalid blocks which might still be slashable despite being invalid.
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum BlockSlashInfo<TErr> {
|
||||
@@ -474,7 +483,7 @@ fn process_block_slash_info<T: BeaconChainTypes>(
|
||||
|
||||
/// Verify all signatures (except deposit signatures) on all blocks in the `chain_segment`. If all
|
||||
/// signatures are valid, the `chain_segment` is mapped to a `Vec<SignatureVerifiedBlock>` that can
|
||||
/// later be transformed into a `FullyVerifiedBlock` without re-checking the signatures. If any
|
||||
/// later be transformed into a `ExecutionPendingBlock` without re-checking the signatures. If any
|
||||
/// signature in the block is invalid, an `Err` is returned (it is not possible to known _which_
|
||||
/// signature was invalid).
|
||||
///
|
||||
@@ -483,7 +492,7 @@ fn process_block_slash_info<T: BeaconChainTypes>(
|
||||
/// The given `chain_segment` must span no more than two epochs, otherwise an error will be
|
||||
/// returned.
|
||||
pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
mut chain_segment: Vec<(Hash256, SignedBeaconBlock<T::EthSpec>)>,
|
||||
mut chain_segment: Vec<(Hash256, Arc<SignedBeaconBlock<T::EthSpec>>)>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Vec<SignatureVerifiedBlock<T>>, BlockError<T::EthSpec>> {
|
||||
if chain_segment.is_empty() {
|
||||
@@ -541,7 +550,7 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = "T: BeaconChainTypes"))]
|
||||
pub struct GossipVerifiedBlock<T: BeaconChainTypes> {
|
||||
pub block: SignedBeaconBlock<T::EthSpec>,
|
||||
pub block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
pub block_root: Hash256,
|
||||
parent: Option<PreProcessingSnapshot<T::EthSpec>>,
|
||||
}
|
||||
@@ -549,11 +558,15 @@ pub struct GossipVerifiedBlock<T: BeaconChainTypes> {
|
||||
/// A wrapper around a `SignedBeaconBlock` that indicates that all signatures (except the deposit
|
||||
/// signatures) have been verified.
|
||||
pub struct SignatureVerifiedBlock<T: BeaconChainTypes> {
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
block_root: Hash256,
|
||||
parent: Option<PreProcessingSnapshot<T::EthSpec>>,
|
||||
}
|
||||
|
||||
/// Used to await the result of executing payload with a remote EE.
|
||||
type PayloadVerificationHandle<E> =
|
||||
JoinHandle<Option<Result<PayloadVerificationOutcome, BlockError<E>>>>;
|
||||
|
||||
/// A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and
|
||||
/// ready to import into the `BeaconChain`. The validation includes:
|
||||
///
|
||||
@@ -562,42 +575,42 @@ pub struct SignatureVerifiedBlock<T: BeaconChainTypes> {
|
||||
/// - State root check
|
||||
/// - Per block processing
|
||||
///
|
||||
/// Note: a `FullyVerifiedBlock` is not _forever_ valid to be imported, it may later become invalid
|
||||
/// due to finality or some other event. A `FullyVerifiedBlock` should be imported into the
|
||||
/// Note: a `ExecutionPendingBlock` is not _forever_ valid to be imported, it may later become invalid
|
||||
/// due to finality or some other event. A `ExecutionPendingBlock` should be imported into the
|
||||
/// `BeaconChain` immediately after it is instantiated.
|
||||
pub struct FullyVerifiedBlock<'a, T: BeaconChainTypes> {
|
||||
pub block: SignedBeaconBlock<T::EthSpec>,
|
||||
pub struct ExecutionPendingBlock<T: BeaconChainTypes> {
|
||||
pub block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
pub block_root: Hash256,
|
||||
pub state: BeaconState<T::EthSpec>,
|
||||
pub parent_block: SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>,
|
||||
pub confirmation_db_batch: Vec<StoreOp<'a, T::EthSpec>>,
|
||||
pub payload_verification_status: PayloadVerificationStatus,
|
||||
pub confirmed_state_roots: Vec<Hash256>,
|
||||
pub payload_verification_handle: PayloadVerificationHandle<T::EthSpec>,
|
||||
}
|
||||
|
||||
/// Implemented on types that can be converted into a `FullyVerifiedBlock`.
|
||||
/// Implemented on types that can be converted into a `ExecutionPendingBlock`.
|
||||
///
|
||||
/// Used to allow functions to accept blocks at various stages of verification.
|
||||
pub trait IntoFullyVerifiedBlock<T: BeaconChainTypes>: Sized {
|
||||
fn into_fully_verified_block(
|
||||
pub trait IntoExecutionPendingBlock<T: BeaconChainTypes>: Sized {
|
||||
fn into_execution_pending_block(
|
||||
self,
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
) -> Result<FullyVerifiedBlock<T>, BlockError<T::EthSpec>> {
|
||||
self.into_fully_verified_block_slashable(chain)
|
||||
.map(|fully_verified| {
|
||||
) -> Result<ExecutionPendingBlock<T>, BlockError<T::EthSpec>> {
|
||||
self.into_execution_pending_block_slashable(chain)
|
||||
.map(|execution_pending| {
|
||||
// Supply valid block to slasher.
|
||||
if let Some(slasher) = chain.slasher.as_ref() {
|
||||
slasher.accept_block_header(fully_verified.block.signed_block_header());
|
||||
slasher.accept_block_header(execution_pending.block.signed_block_header());
|
||||
}
|
||||
fully_verified
|
||||
execution_pending
|
||||
})
|
||||
.map_err(|slash_info| process_block_slash_info(chain, slash_info))
|
||||
}
|
||||
|
||||
/// Convert the block to fully-verified form while producing data to aid checking slashability.
|
||||
fn into_fully_verified_block_slashable(
|
||||
fn into_execution_pending_block_slashable(
|
||||
self,
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
) -> Result<FullyVerifiedBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>>;
|
||||
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>>;
|
||||
|
||||
fn block(&self) -> &SignedBeaconBlock<T::EthSpec>;
|
||||
}
|
||||
@@ -608,7 +621,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
///
|
||||
/// Returns an error if the block is invalid, or if the block was unable to be verified.
|
||||
pub fn new(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
// If the block is valid for gossip we don't supply it to the slasher here because
|
||||
@@ -623,7 +636,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
|
||||
/// As for new, but doesn't pass the block to the slasher.
|
||||
fn new_without_slasher_checks(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
// Ensure the block is the correct structure for the fork at `block.slot()`.
|
||||
@@ -658,7 +671,11 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
// reboot if the `observed_block_producers` cache is empty. In that case, without this
|
||||
// check, we will load the parent and state from disk only to find out later that we
|
||||
// already know this block.
|
||||
if chain.fork_choice.read().contains_block(&block_root) {
|
||||
if chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.contains_block(&block_root)
|
||||
{
|
||||
return Err(BlockError::BlockIsAlreadyKnown);
|
||||
}
|
||||
|
||||
@@ -678,10 +695,10 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
// 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.
|
||||
let block = check_block_is_finalized_descendant::<T, _>(
|
||||
block,
|
||||
&chain.fork_choice.read(),
|
||||
&chain.store,
|
||||
check_block_is_finalized_descendant(
|
||||
chain,
|
||||
&chain.canonical_head.fork_choice_write_lock(),
|
||||
&block,
|
||||
)?;
|
||||
|
||||
let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||
@@ -827,15 +844,15 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> IntoFullyVerifiedBlock<T> for GossipVerifiedBlock<T> {
|
||||
impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for GossipVerifiedBlock<T> {
|
||||
/// Completes verification of the wrapped `block`.
|
||||
fn into_fully_verified_block_slashable(
|
||||
fn into_execution_pending_block_slashable(
|
||||
self,
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
) -> Result<FullyVerifiedBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
let fully_verified =
|
||||
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
let execution_pending =
|
||||
SignatureVerifiedBlock::from_gossip_verified_block_check_slashable(self, chain)?;
|
||||
fully_verified.into_fully_verified_block_slashable(chain)
|
||||
execution_pending.into_execution_pending_block_slashable(chain)
|
||||
}
|
||||
|
||||
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {
|
||||
@@ -849,7 +866,7 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
|
||||
///
|
||||
/// Returns an error if the block is invalid, or if the block was unable to be verified.
|
||||
pub fn new(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
block_root: Hash256,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
@@ -892,7 +909,7 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
|
||||
|
||||
/// As for `new` above but producing `BlockSlashInfo`.
|
||||
pub fn check_slashable(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
block_root: Hash256,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Self, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
@@ -947,12 +964,12 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> IntoFullyVerifiedBlock<T> for SignatureVerifiedBlock<T> {
|
||||
impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for SignatureVerifiedBlock<T> {
|
||||
/// Completes verification of the wrapped `block`.
|
||||
fn into_fully_verified_block_slashable(
|
||||
fn into_execution_pending_block_slashable(
|
||||
self,
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
) -> Result<FullyVerifiedBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
let header = self.block.signed_block_header();
|
||||
let (parent, block) = if let Some(parent) = self.parent {
|
||||
(parent, self.block)
|
||||
@@ -961,7 +978,7 @@ impl<T: BeaconChainTypes> IntoFullyVerifiedBlock<T> for SignatureVerifiedBlock<T
|
||||
.map_err(|e| BlockSlashInfo::SignatureValid(header.clone(), e))?
|
||||
};
|
||||
|
||||
FullyVerifiedBlock::from_signature_verified_components(
|
||||
ExecutionPendingBlock::from_signature_verified_components(
|
||||
block,
|
||||
self.block_root,
|
||||
parent,
|
||||
@@ -975,19 +992,19 @@ impl<T: BeaconChainTypes> IntoFullyVerifiedBlock<T> for SignatureVerifiedBlock<T
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> IntoFullyVerifiedBlock<T> for SignedBeaconBlock<T::EthSpec> {
|
||||
impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for Arc<SignedBeaconBlock<T::EthSpec>> {
|
||||
/// Verifies the `SignedBeaconBlock` by first transforming it into a `SignatureVerifiedBlock`
|
||||
/// and then using that implementation of `IntoFullyVerifiedBlock` to complete verification.
|
||||
fn into_fully_verified_block_slashable(
|
||||
/// and then using that implementation of `IntoExecutionPendingBlock` to complete verification.
|
||||
fn into_execution_pending_block_slashable(
|
||||
self,
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
) -> Result<FullyVerifiedBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
|
||||
// Perform an early check to prevent wasting time on irrelevant blocks.
|
||||
let block_root = check_block_relevancy(&self, None, chain)
|
||||
.map_err(|e| BlockSlashInfo::SignatureNotChecked(self.signed_block_header(), e))?;
|
||||
|
||||
SignatureVerifiedBlock::check_slashable(self, block_root, chain)?
|
||||
.into_fully_verified_block_slashable(chain)
|
||||
.into_execution_pending_block_slashable(chain)
|
||||
}
|
||||
|
||||
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {
|
||||
@@ -995,7 +1012,7 @@ impl<T: BeaconChainTypes> IntoFullyVerifiedBlock<T> for SignedBeaconBlock<T::Eth
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
|
||||
/// Instantiates `Self`, a wrapper that indicates that the given `block` is fully valid. See
|
||||
/// the struct-level documentation for more information.
|
||||
///
|
||||
@@ -1004,12 +1021,16 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
///
|
||||
/// Returns an error if the block is invalid, or if the block was unable to be verified.
|
||||
pub fn from_signature_verified_components(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
block_root: Hash256,
|
||||
parent: PreProcessingSnapshot<T::EthSpec>,
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
if let Some(parent) = chain.fork_choice.read().get_block(&block.parent_root()) {
|
||||
if let Some(parent) = chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&block.parent_root())
|
||||
{
|
||||
// Reject any block where the parent has an invalid payload. It's impossible for a valid
|
||||
// block to descend from an invalid parent.
|
||||
if parent.execution_status.is_invalid() {
|
||||
@@ -1028,7 +1049,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
// because it will revert finalization. Note that the finalized block is stored in fork
|
||||
// choice, so we will not reject any child of the finalized block (this is relevant during
|
||||
// genesis).
|
||||
return Err(BlockError::ParentUnknown(Box::new(block)));
|
||||
return Err(BlockError::ParentUnknown(block));
|
||||
}
|
||||
|
||||
// Reject any block that exceeds our limit on skipped slots.
|
||||
@@ -1048,7 +1069,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
|
||||
// Stage a batch of operations to be completed atomically if this block is imported
|
||||
// successfully.
|
||||
let mut confirmation_db_batch = vec![];
|
||||
let mut confirmed_state_roots = vec![];
|
||||
|
||||
// The block must have a higher slot than its parent.
|
||||
if block.slot() <= parent.beacon_block.slot() {
|
||||
@@ -1121,7 +1142,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
chain.store.do_atomically(state_batch)?;
|
||||
drop(txn_lock);
|
||||
|
||||
confirmation_db_batch.push(StoreOp::DeleteStateTemporaryFlag(state_root));
|
||||
confirmed_state_roots.push(state_root);
|
||||
|
||||
state_root
|
||||
};
|
||||
@@ -1140,59 +1161,82 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// If this block triggers the merge, check to ensure that it references valid execution
|
||||
// blocks.
|
||||
//
|
||||
// The specification defines this check inside `on_block` in the fork-choice specification,
|
||||
// however we perform the check here for two reasons:
|
||||
//
|
||||
// - There's no point in importing a block that will fail fork choice, so it's best to fail
|
||||
// early.
|
||||
// - Doing the check here means we can keep our fork-choice implementation "pure". I.e., no
|
||||
// calls to remote servers.
|
||||
let valid_merge_transition_block =
|
||||
if is_merge_transition_block(&state, block.message().body()) {
|
||||
validate_merge_block(chain, block.message())?;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
let block_slot = block.slot();
|
||||
let state_current_epoch = state.current_epoch();
|
||||
|
||||
// Define a future that will verify the execution payload with an execution engine (but
|
||||
// don't execute it yet).
|
||||
let payload_notifier = PayloadNotifier::new(chain.clone(), block.clone(), &state)?;
|
||||
let is_valid_merge_transition_block =
|
||||
is_merge_transition_block(&state, block.message().body());
|
||||
let payload_verification_future = async move {
|
||||
let chain = payload_notifier.chain.clone();
|
||||
let block = payload_notifier.block.clone();
|
||||
|
||||
// If this block triggers the merge, check to ensure that it references valid execution
|
||||
// blocks.
|
||||
//
|
||||
// The specification defines this check inside `on_block` in the fork-choice specification,
|
||||
// however we perform the check here for two reasons:
|
||||
//
|
||||
// - There's no point in importing a block that will fail fork choice, so it's best to fail
|
||||
// early.
|
||||
// - Doing the check here means we can keep our fork-choice implementation "pure". I.e., no
|
||||
// calls to remote servers.
|
||||
if is_valid_merge_transition_block {
|
||||
validate_merge_block(&chain, block.message()).await?;
|
||||
};
|
||||
|
||||
// The specification declares that this should be run *inside* `per_block_processing`,
|
||||
// however we run it here to keep `per_block_processing` pure (i.e., no calls to external
|
||||
// servers).
|
||||
//
|
||||
// It is important that this function is called *after* `per_slot_processing`, since the
|
||||
// `randao` may change.
|
||||
let payload_verification_status = notify_new_payload(chain, &state, block.message())?;
|
||||
// The specification declares that this should be run *inside* `per_block_processing`,
|
||||
// however we run it here to keep `per_block_processing` pure (i.e., no calls to external
|
||||
// servers).
|
||||
//
|
||||
// It is important that this function is called *after* `per_slot_processing`, since the
|
||||
// `randao` may change.
|
||||
let payload_verification_status = payload_notifier.notify_new_payload().await?;
|
||||
|
||||
// If the payload did not validate or invalidate the block, check to see if this block is
|
||||
// valid for optimistic import.
|
||||
if payload_verification_status.is_optimistic() {
|
||||
let current_slot = chain
|
||||
.slot_clock
|
||||
.now()
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
// If the payload did not validate or invalidate the block, check to see if this block is
|
||||
// valid for optimistic import.
|
||||
if payload_verification_status.is_optimistic() {
|
||||
let block_hash_opt = block
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.map(|full_payload| full_payload.execution_payload.block_hash);
|
||||
|
||||
if !chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.is_optimistic_candidate_block(
|
||||
current_slot,
|
||||
block.slot(),
|
||||
&block.parent_root(),
|
||||
&chain.spec,
|
||||
)
|
||||
.map_err(BeaconChainError::from)?
|
||||
{
|
||||
return Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into());
|
||||
// Ensure the block is a candidate for optimistic import.
|
||||
if !is_optimistic_candidate_block(&chain, block.slot(), block.parent_root()).await?
|
||||
{
|
||||
warn!(
|
||||
chain.log,
|
||||
"Rejecting optimistic block";
|
||||
"block_hash" => ?block_hash_opt,
|
||||
"msg" => "the execution engine is not synced"
|
||||
);
|
||||
return Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PayloadVerificationOutcome {
|
||||
payload_verification_status,
|
||||
is_valid_merge_transition_block,
|
||||
})
|
||||
};
|
||||
// Spawn the payload verification future as a new task, but don't wait for it to complete.
|
||||
// The `payload_verification_future` will be awaited later to ensure verification completed
|
||||
// successfully.
|
||||
let payload_verification_handle = chain
|
||||
.task_executor
|
||||
.spawn_handle(
|
||||
payload_verification_future,
|
||||
"execution_payload_verification",
|
||||
)
|
||||
.ok_or(BeaconChainError::RuntimeShutdown)?;
|
||||
|
||||
// If the block is sufficiently recent, notify the validator monitor.
|
||||
if let Some(slot) = chain.slot_clock.now() {
|
||||
let epoch = slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
if block.slot().epoch(T::EthSpec::slots_per_epoch())
|
||||
if block_slot.epoch(T::EthSpec::slots_per_epoch())
|
||||
+ VALIDATOR_MONITOR_HISTORIC_EPOCHS as u64
|
||||
>= epoch
|
||||
{
|
||||
@@ -1201,7 +1245,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
// the `validator_monitor` lock from being bounced or held for a long time whilst
|
||||
// performing `per_slot_processing`.
|
||||
for (i, summary) in summaries.iter().enumerate() {
|
||||
let epoch = state.current_epoch() - Epoch::from(summaries.len() - i);
|
||||
let epoch = state_current_epoch - Epoch::from(summaries.len() - i);
|
||||
if let Err(e) =
|
||||
validator_monitor.process_validator_statuses(epoch, summary, &chain.spec)
|
||||
{
|
||||
@@ -1300,21 +1344,13 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
});
|
||||
}
|
||||
|
||||
if valid_merge_transition_block {
|
||||
info!(chain.log, "{}", POS_PANDA_BANNER);
|
||||
info!(chain.log, "Proof of Stake Activated"; "slot" => block.slot());
|
||||
info!(chain.log, ""; "Terminal POW Block Hash" => ?block.message().execution_payload()?.parent_hash().into_root());
|
||||
info!(chain.log, ""; "Merge Transition Block Root" => ?block.message().tree_hash_root());
|
||||
info!(chain.log, ""; "Merge Transition Execution Hash" => ?block.message().execution_payload()?.block_hash().into_root());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
block,
|
||||
block_root,
|
||||
state,
|
||||
parent_block: parent.beacon_block,
|
||||
confirmation_db_batch,
|
||||
payload_verification_status,
|
||||
confirmed_state_roots,
|
||||
payload_verification_handle,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1366,8 +1402,9 @@ fn check_block_against_finalized_slot<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<(), BlockError<T::EthSpec>> {
|
||||
let finalized_slot = chain
|
||||
.head_info()?
|
||||
.finalized_checkpoint
|
||||
.canonical_head
|
||||
.cached_head()
|
||||
.finalized_checkpoint()
|
||||
.epoch
|
||||
.start_slot(T::EthSpec::slots_per_epoch());
|
||||
|
||||
@@ -1383,13 +1420,17 @@ fn check_block_against_finalized_slot<T: BeaconChainTypes>(
|
||||
}
|
||||
|
||||
/// Returns `Ok(block)` if the block descends from the finalized root.
|
||||
pub fn check_block_is_finalized_descendant<T: BeaconChainTypes, F: ForkChoiceStore<T::EthSpec>>(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
fork_choice: &ForkChoice<F, T::EthSpec>,
|
||||
store: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
|
||||
) -> Result<SignedBeaconBlock<T::EthSpec>, BlockError<T::EthSpec>> {
|
||||
///
|
||||
/// ## Warning
|
||||
///
|
||||
/// Taking a lock on the `chain.canonical_head.fork_choice` might cause a deadlock here.
|
||||
pub fn check_block_is_finalized_descendant<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
fork_choice: &BeaconForkChoice<T>,
|
||||
block: &Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
) -> Result<(), BlockError<T::EthSpec>> {
|
||||
if fork_choice.is_descendant_of_finalized(block.parent_root()) {
|
||||
Ok(block)
|
||||
Ok(())
|
||||
} else {
|
||||
// If fork choice does *not* consider the parent to be a descendant of the finalized block,
|
||||
// then there are two more cases:
|
||||
@@ -1399,7 +1440,8 @@ pub fn check_block_is_finalized_descendant<T: BeaconChainTypes, F: ForkChoiceSto
|
||||
// pre-finalization or conflicting with finalization.
|
||||
// 2. The parent is unknown to us, we probably want to download it since it might actually
|
||||
// descend from the finalized root.
|
||||
if store
|
||||
if chain
|
||||
.store
|
||||
.block_exists(&block.parent_root())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||
{
|
||||
@@ -1407,7 +1449,7 @@ pub fn check_block_is_finalized_descendant<T: BeaconChainTypes, F: ForkChoiceSto
|
||||
block_parent_root: block.parent_root(),
|
||||
})
|
||||
} else {
|
||||
Err(BlockError::ParentUnknown(Box::new(block)))
|
||||
Err(BlockError::ParentUnknown(block.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1452,7 +1494,11 @@ pub fn check_block_relevancy<T: BeaconChainTypes>(
|
||||
|
||||
// Check if the block is already known. We know it is post-finalization, so it is
|
||||
// sufficient to check the fork choice.
|
||||
if chain.fork_choice.read().contains_block(&block_root) {
|
||||
if chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.contains_block(&block_root)
|
||||
{
|
||||
return Err(BlockError::BlockIsAlreadyKnown);
|
||||
}
|
||||
|
||||
@@ -1477,16 +1523,16 @@ pub fn get_block_root<E: EthSpec>(block: &SignedBeaconBlock<E>) -> Hash256 {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn verify_parent_block_is_known<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
) -> Result<(ProtoBlock, SignedBeaconBlock<T::EthSpec>), BlockError<T::EthSpec>> {
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
) -> Result<(ProtoBlock, Arc<SignedBeaconBlock<T::EthSpec>>), BlockError<T::EthSpec>> {
|
||||
if let Some(proto_block) = chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&block.message().parent_root())
|
||||
{
|
||||
Ok((proto_block, block))
|
||||
} else {
|
||||
Err(BlockError::ParentUnknown(Box::new(block)))
|
||||
Err(BlockError::ParentUnknown(block))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1496,12 +1542,12 @@ fn verify_parent_block_is_known<T: BeaconChainTypes>(
|
||||
/// whilst attempting the operation.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn load_parent<T: BeaconChainTypes>(
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<
|
||||
(
|
||||
PreProcessingSnapshot<T::EthSpec>,
|
||||
SignedBeaconBlock<T::EthSpec>,
|
||||
Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
),
|
||||
BlockError<T::EthSpec>,
|
||||
> {
|
||||
@@ -1518,11 +1564,11 @@ fn load_parent<T: BeaconChainTypes>(
|
||||
// choice, so we will not reject any child of the finalized block (this is relevant during
|
||||
// genesis).
|
||||
if !chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.contains_block(&block.parent_root())
|
||||
{
|
||||
return Err(BlockError::ParentUnknown(Box::new(block)));
|
||||
return Err(BlockError::ParentUnknown(block));
|
||||
}
|
||||
|
||||
let block_delay = chain
|
||||
@@ -1717,18 +1763,12 @@ fn verify_header_signature<T: BeaconChainTypes>(
|
||||
.get(header.message.proposer_index as usize)
|
||||
.cloned()
|
||||
.ok_or(BlockError::UnknownValidator(header.message.proposer_index))?;
|
||||
let (fork, genesis_validators_root) =
|
||||
chain.with_head::<_, BlockError<T::EthSpec>, _>(|head| {
|
||||
Ok((
|
||||
head.beacon_state.fork(),
|
||||
head.beacon_state.genesis_validators_root(),
|
||||
))
|
||||
})?;
|
||||
let head_fork = chain.canonical_head.cached_head().head_fork();
|
||||
|
||||
if header.verify_signature::<T::EthSpec>(
|
||||
&proposer_pubkey,
|
||||
&fork,
|
||||
genesis_validators_root,
|
||||
&head_fork,
|
||||
chain.genesis_validators_root,
|
||||
&chain.spec,
|
||||
) {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, OP_POOL_DB_KEY};
|
||||
use crate::beacon_chain::{CanonicalHead, BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, OP_POOL_DB_KEY};
|
||||
use crate::eth1_chain::{CachingEth1Backend, SszEth1};
|
||||
use crate::fork_choice_signal::ForkChoiceSignalTx;
|
||||
use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary};
|
||||
@@ -245,6 +245,7 @@ where
|
||||
let fork_choice =
|
||||
BeaconChain::<Witness<TSlotClock, TEth1Backend, _, _, _>>::load_fork_choice(
|
||||
store.clone(),
|
||||
&self.spec,
|
||||
)
|
||||
.map_err(|e| format!("Unable to load fork choice from disk: {:?}", e))?
|
||||
.ok_or("Fork choice not found in store")?;
|
||||
@@ -337,7 +338,7 @@ where
|
||||
Ok((
|
||||
BeaconSnapshot {
|
||||
beacon_block_root,
|
||||
beacon_block,
|
||||
beacon_block: Arc::new(beacon_block),
|
||||
beacon_state,
|
||||
},
|
||||
self,
|
||||
@@ -352,12 +353,15 @@ where
|
||||
self = updated_builder;
|
||||
|
||||
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis);
|
||||
let current_slot = None;
|
||||
|
||||
let fork_choice = ForkChoice::from_anchor(
|
||||
fc_store,
|
||||
genesis.beacon_block_root,
|
||||
&genesis.beacon_block,
|
||||
&genesis.beacon_state,
|
||||
current_slot,
|
||||
&self.spec,
|
||||
)
|
||||
.map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?;
|
||||
|
||||
@@ -455,17 +459,20 @@ where
|
||||
|
||||
let snapshot = BeaconSnapshot {
|
||||
beacon_block_root: weak_subj_block_root,
|
||||
beacon_block: weak_subj_block,
|
||||
beacon_block: Arc::new(weak_subj_block),
|
||||
beacon_state: weak_subj_state,
|
||||
};
|
||||
|
||||
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &snapshot);
|
||||
|
||||
let current_slot = Some(snapshot.beacon_block.slot());
|
||||
let fork_choice = ForkChoice::from_anchor(
|
||||
fc_store,
|
||||
snapshot.beacon_block_root,
|
||||
&snapshot.beacon_block,
|
||||
&snapshot.beacon_state,
|
||||
current_slot,
|
||||
&self.spec,
|
||||
)
|
||||
.map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?;
|
||||
|
||||
@@ -638,17 +645,18 @@ where
|
||||
head_block_root,
|
||||
&head_state,
|
||||
store.clone(),
|
||||
Some(current_slot),
|
||||
&self.spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut canonical_head = BeaconSnapshot {
|
||||
let mut head_snapshot = BeaconSnapshot {
|
||||
beacon_block_root: head_block_root,
|
||||
beacon_block: head_block,
|
||||
beacon_block: Arc::new(head_block),
|
||||
beacon_state: head_state,
|
||||
};
|
||||
|
||||
canonical_head
|
||||
head_snapshot
|
||||
.beacon_state
|
||||
.build_all_caches(&self.spec)
|
||||
.map_err(|e| format!("Failed to build state caches: {:?}", e))?;
|
||||
@@ -658,25 +666,17 @@ where
|
||||
//
|
||||
// This is a sanity check to detect database corruption.
|
||||
let fc_finalized = fork_choice.finalized_checkpoint();
|
||||
let head_finalized = canonical_head.beacon_state.finalized_checkpoint();
|
||||
if fc_finalized != head_finalized {
|
||||
let is_genesis = head_finalized.root.is_zero()
|
||||
&& head_finalized.epoch == fc_finalized.epoch
|
||||
&& fc_finalized.root == genesis_block_root;
|
||||
let is_wss = store.get_anchor_slot().map_or(false, |anchor_slot| {
|
||||
fc_finalized.epoch == anchor_slot.epoch(TEthSpec::slots_per_epoch())
|
||||
});
|
||||
if !is_genesis && !is_wss {
|
||||
return Err(format!(
|
||||
"Database corrupt: fork choice is finalized at {:?} whilst head is finalized at \
|
||||
let head_finalized = head_snapshot.beacon_state.finalized_checkpoint();
|
||||
if fc_finalized.epoch < head_finalized.epoch {
|
||||
return Err(format!(
|
||||
"Database corrupt: fork choice is finalized at {:?} whilst head is finalized at \
|
||||
{:?}",
|
||||
fc_finalized, head_finalized
|
||||
));
|
||||
}
|
||||
fc_finalized, head_finalized
|
||||
));
|
||||
}
|
||||
|
||||
let validator_pubkey_cache = self.validator_pubkey_cache.map(Ok).unwrap_or_else(|| {
|
||||
ValidatorPubkeyCache::new(&canonical_head.beacon_state, store.clone())
|
||||
ValidatorPubkeyCache::new(&head_snapshot.beacon_state, store.clone())
|
||||
.map_err(|e| format!("Unable to init validator pubkey cache: {:?}", e))
|
||||
})?;
|
||||
|
||||
@@ -691,7 +691,7 @@ where
|
||||
if let Some(slot) = slot_clock.now() {
|
||||
validator_monitor.process_valid_state(
|
||||
slot.epoch(TEthSpec::slots_per_epoch()),
|
||||
&canonical_head.beacon_state,
|
||||
&head_snapshot.beacon_state,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -725,10 +725,18 @@ where
|
||||
.do_atomically(self.pending_io_batch)
|
||||
.map_err(|e| format!("Error writing chain & metadata to disk: {:?}", e))?;
|
||||
|
||||
let genesis_validators_root = head_snapshot.beacon_state.genesis_validators_root();
|
||||
let genesis_time = head_snapshot.beacon_state.genesis_time();
|
||||
let head_for_snapshot_cache = head_snapshot.clone();
|
||||
let canonical_head = CanonicalHead::new(fork_choice, Arc::new(head_snapshot));
|
||||
|
||||
let beacon_chain = BeaconChain {
|
||||
spec: self.spec,
|
||||
config: self.chain_config,
|
||||
store,
|
||||
task_executor: self
|
||||
.task_executor
|
||||
.ok_or("Cannot build without task executor")?,
|
||||
store_migrator,
|
||||
slot_clock,
|
||||
op_pool: self.op_pool.ok_or("Cannot build without op pool")?,
|
||||
@@ -758,18 +766,18 @@ where
|
||||
observed_attester_slashings: <_>::default(),
|
||||
eth1_chain: self.eth1_chain,
|
||||
execution_layer: self.execution_layer,
|
||||
genesis_validators_root: canonical_head.beacon_state.genesis_validators_root(),
|
||||
canonical_head: TimeoutRwLock::new(canonical_head.clone()),
|
||||
genesis_validators_root,
|
||||
genesis_time,
|
||||
canonical_head,
|
||||
genesis_block_root,
|
||||
genesis_state_root,
|
||||
fork_choice: RwLock::new(fork_choice),
|
||||
fork_choice_signal_tx,
|
||||
fork_choice_signal_rx,
|
||||
event_handler: self.event_handler,
|
||||
head_tracker,
|
||||
snapshot_cache: TimeoutRwLock::new(SnapshotCache::new(
|
||||
DEFAULT_SNAPSHOT_CACHE_SIZE,
|
||||
canonical_head,
|
||||
head_for_snapshot_cache,
|
||||
)),
|
||||
shuffling_cache: TimeoutRwLock::new(ShufflingCache::new()),
|
||||
beacon_proposer_cache: <_>::default(),
|
||||
@@ -787,9 +795,7 @@ where
|
||||
validator_monitor: RwLock::new(validator_monitor),
|
||||
};
|
||||
|
||||
let head = beacon_chain
|
||||
.head()
|
||||
.map_err(|e| format!("Failed to get head: {:?}", e))?;
|
||||
let head = beacon_chain.head_snapshot();
|
||||
|
||||
// Prime the attester cache with the head state.
|
||||
beacon_chain
|
||||
@@ -992,10 +998,10 @@ mod test {
|
||||
.build()
|
||||
.expect("should build");
|
||||
|
||||
let head = chain.head().expect("should get head");
|
||||
let head = chain.head_snapshot();
|
||||
|
||||
let state = head.beacon_state;
|
||||
let block = head.beacon_block;
|
||||
let state = &head.beacon_state;
|
||||
let block = &head.beacon_block;
|
||||
|
||||
assert_eq!(state.slot(), Slot::new(0), "should start from genesis");
|
||||
assert_eq!(
|
||||
@@ -1014,7 +1020,7 @@ mod test {
|
||||
.get_blinded_block(&Hash256::zero())
|
||||
.expect("should read db")
|
||||
.expect("should find genesis block"),
|
||||
block.clone().into(),
|
||||
block.clone_as_blinded(),
|
||||
"should store genesis block under zero hash alias"
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
1307
beacon_node/beacon_chain/src/canonical_head.rs
Normal file
1307
beacon_node/beacon_chain/src/canonical_head.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ use crate::{
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use proto_array::Block as ProtoBlock;
|
||||
use std::sync::Arc;
|
||||
use types::*;
|
||||
|
||||
pub struct CacheItem<E: EthSpec> {
|
||||
@@ -18,7 +19,7 @@ pub struct CacheItem<E: EthSpec> {
|
||||
/*
|
||||
* Values used to make the block available.
|
||||
*/
|
||||
block: SignedBeaconBlock<E>,
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
proto_block: ProtoBlock,
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ impl<E: EthSpec> EarlyAttesterCache<E> {
|
||||
pub fn add_head_block(
|
||||
&self,
|
||||
beacon_block_root: Hash256,
|
||||
block: SignedBeaconBlock<E>,
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
proto_block: ProtoBlock,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
@@ -146,7 +147,7 @@ impl<E: EthSpec> EarlyAttesterCache<E> {
|
||||
}
|
||||
|
||||
/// Returns the block, if `block_root` matches the cached item.
|
||||
pub fn get_block(&self, block_root: Hash256) -> Option<SignedBeaconBlock<E>> {
|
||||
pub fn get_block(&self, block_root: Hash256) -> Option<Arc<SignedBeaconBlock<E>>> {
|
||||
self.item
|
||||
.read()
|
||||
.as_ref()
|
||||
|
||||
@@ -45,8 +45,8 @@ pub enum BeaconChainError {
|
||||
UnableToReadSlot,
|
||||
UnableToComputeTimeAtSlot,
|
||||
RevertedFinalizedEpoch {
|
||||
previous_epoch: Epoch,
|
||||
new_epoch: Epoch,
|
||||
old: Checkpoint,
|
||||
new: Checkpoint,
|
||||
},
|
||||
SlotClockDidNotStart,
|
||||
NoStateForSlot(Slot),
|
||||
@@ -161,6 +161,7 @@ pub enum BeaconChainError {
|
||||
BlockRewardSyncError,
|
||||
HeadMissingFromForkChoice(Hash256),
|
||||
FinalizedBlockMissingFromForkChoice(Hash256),
|
||||
HeadBlockMissingFromForkChoice(Hash256),
|
||||
InvalidFinalizedPayload {
|
||||
finalized_root: Hash256,
|
||||
execution_block_hash: ExecutionBlockHash,
|
||||
@@ -184,11 +185,19 @@ pub enum BeaconChainError {
|
||||
beacon_block_root: Hash256,
|
||||
},
|
||||
RuntimeShutdown,
|
||||
TokioJoin(tokio::task::JoinError),
|
||||
ProcessInvalidExecutionPayload(JoinError),
|
||||
ForkChoiceSignalOutOfOrder {
|
||||
current: Slot,
|
||||
latest: Slot,
|
||||
},
|
||||
ForkchoiceUpdateParamsMissing,
|
||||
HeadHasInvalidPayload {
|
||||
block_root: Hash256,
|
||||
execution_status: ExecutionStatus,
|
||||
},
|
||||
AttestationHeadNotInForkChoice(Hash256),
|
||||
MissingPersistedForkChoice,
|
||||
}
|
||||
|
||||
easy_from_to!(SlotProcessingError, BeaconChainError);
|
||||
@@ -214,7 +223,6 @@ easy_from_to!(BlockReplayError, BeaconChainError);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlockProductionError {
|
||||
UnableToGetHeadInfo(BeaconChainError),
|
||||
UnableToGetBlockRootFromState,
|
||||
UnableToReadSlot,
|
||||
UnableToProduceAtSlot(Slot),
|
||||
@@ -239,6 +247,11 @@ pub enum BlockProductionError {
|
||||
MissingFinalizedBlock(Hash256),
|
||||
BlockTooLarge(usize),
|
||||
ForkChoiceError(BeaconChainError),
|
||||
ShuttingDown,
|
||||
MissingSyncAggregate,
|
||||
MissingExecutionPayload,
|
||||
TokioJoin(tokio::task::JoinError),
|
||||
BeaconChain(BeaconChainError),
|
||||
}
|
||||
|
||||
easy_from_to!(BlockProcessingError, BlockProductionError);
|
||||
|
||||
@@ -21,8 +21,59 @@ use state_processing::per_block_processing::{
|
||||
partially_verify_execution_payload,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
use types::*;
|
||||
|
||||
pub type PreparePayloadResult<Payload> = Result<Payload, BlockProductionError>;
|
||||
pub type PreparePayloadHandle<Payload> = JoinHandle<Option<PreparePayloadResult<Payload>>>;
|
||||
|
||||
/// Used to await the result of executing payload with a remote EE.
|
||||
pub struct PayloadNotifier<T: BeaconChainTypes> {
|
||||
pub chain: Arc<BeaconChain<T>>,
|
||||
pub block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
payload_verification_status: Option<PayloadVerificationStatus>,
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> PayloadNotifier<T> {
|
||||
pub fn new(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
let payload_verification_status = if is_execution_enabled(state, block.message().body()) {
|
||||
// Perform the initial stages of payload verification.
|
||||
//
|
||||
// We will duplicate these checks again during `per_block_processing`, however these checks
|
||||
// are cheap and doing them here ensures we protect the execution engine from junk.
|
||||
partially_verify_execution_payload(
|
||||
state,
|
||||
block.message().execution_payload()?,
|
||||
&chain.spec,
|
||||
)
|
||||
.map_err(BlockError::PerBlockProcessingError)?;
|
||||
None
|
||||
} else {
|
||||
Some(PayloadVerificationStatus::Irrelevant)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
chain,
|
||||
block,
|
||||
payload_verification_status,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn notify_new_payload(
|
||||
self,
|
||||
) -> Result<PayloadVerificationStatus, BlockError<T::EthSpec>> {
|
||||
if let Some(precomputed_status) = self.payload_verification_status {
|
||||
Ok(precomputed_status)
|
||||
} else {
|
||||
notify_new_payload(&self.chain, self.block.message()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that `execution_payload` contained by `block` is considered valid by an execution
|
||||
/// engine.
|
||||
///
|
||||
@@ -32,31 +83,20 @@ use types::*;
|
||||
/// contains a few extra checks by running `partially_verify_execution_payload` first:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/v1.1.9/specs/bellatrix/beacon-chain.md#notify_new_payload
|
||||
pub fn notify_new_payload<T: BeaconChainTypes>(
|
||||
async fn notify_new_payload<'a, T: BeaconChainTypes>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
block: BeaconBlockRef<T::EthSpec>,
|
||||
block: BeaconBlockRef<'a, T::EthSpec>,
|
||||
) -> Result<PayloadVerificationStatus, BlockError<T::EthSpec>> {
|
||||
if !is_execution_enabled(state, block.body()) {
|
||||
return Ok(PayloadVerificationStatus::Irrelevant);
|
||||
}
|
||||
|
||||
let execution_payload = block.execution_payload()?;
|
||||
|
||||
// Perform the initial stages of payload verification.
|
||||
//
|
||||
// We will duplicate these checks again during `per_block_processing`, however these checks
|
||||
// are cheap and doing them here ensures we protect the execution payload from junk.
|
||||
partially_verify_execution_payload(state, execution_payload, &chain.spec)
|
||||
.map_err(BlockError::PerBlockProcessingError)?;
|
||||
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
|
||||
let new_payload_response = execution_layer.block_on(|execution_layer| {
|
||||
execution_layer.notify_new_payload(&execution_payload.execution_payload)
|
||||
});
|
||||
|
||||
let new_payload_response = execution_layer
|
||||
.notify_new_payload(&execution_payload.execution_payload)
|
||||
.await;
|
||||
|
||||
match new_payload_response {
|
||||
Ok(status) => match status {
|
||||
@@ -70,13 +110,13 @@ pub fn notify_new_payload<T: BeaconChainTypes>(
|
||||
// This block has not yet been applied to fork choice, so the latest block that was
|
||||
// imported to fork choice was the parent.
|
||||
let latest_root = block.parent_root();
|
||||
chain.process_invalid_execution_payload(
|
||||
&InvalidationOperation::InvalidateMany {
|
||||
chain
|
||||
.process_invalid_execution_payload(&InvalidationOperation::InvalidateMany {
|
||||
head_block_root: latest_root,
|
||||
always_invalidate_head: false,
|
||||
latest_valid_ancestor: latest_valid_hash,
|
||||
},
|
||||
)?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
|
||||
}
|
||||
@@ -103,9 +143,9 @@ pub fn notify_new_payload<T: BeaconChainTypes>(
|
||||
/// Equivalent to the `validate_merge_block` function in the merge Fork Choice Changes:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/fork-choice.md#validate_merge_block
|
||||
pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
block: BeaconBlockRef<T::EthSpec>,
|
||||
pub async fn validate_merge_block<'a, T: BeaconChainTypes>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
block: BeaconBlockRef<'a, T::EthSpec>,
|
||||
) -> Result<(), BlockError<T::EthSpec>> {
|
||||
let spec = &chain.spec;
|
||||
let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||
@@ -137,9 +177,8 @@ pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
|
||||
|
||||
let is_valid_terminal_pow_block = execution_layer
|
||||
.block_on(|execution_layer| {
|
||||
execution_layer.is_valid_terminal_pow_block_hash(execution_payload.parent_hash(), spec)
|
||||
})
|
||||
.is_valid_terminal_pow_block_hash(execution_payload.parent_hash(), spec)
|
||||
.await
|
||||
.map_err(ExecutionPayloadError::from)?;
|
||||
|
||||
match is_valid_terminal_pow_block {
|
||||
@@ -149,23 +188,7 @@ pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
}
|
||||
.into()),
|
||||
None => {
|
||||
let current_slot = chain
|
||||
.slot_clock
|
||||
.now()
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
|
||||
// Ensure the block is a candidate for optimistic import.
|
||||
if chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.is_optimistic_candidate_block(
|
||||
current_slot,
|
||||
block.slot(),
|
||||
&block.parent_root(),
|
||||
&chain.spec,
|
||||
)
|
||||
.map_err(BeaconChainError::from)?
|
||||
{
|
||||
if is_optimistic_candidate_block(chain, block.slot(), block.parent_root()).await? {
|
||||
debug!(
|
||||
chain.log,
|
||||
"Optimistically accepting terminal block";
|
||||
@@ -180,6 +203,36 @@ pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Check to see if a block with the given parameters is valid to be imported optimistically.
|
||||
pub async fn is_optimistic_candidate_block<T: BeaconChainTypes>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
block_slot: Slot,
|
||||
block_parent_root: Hash256,
|
||||
) -> Result<bool, BeaconChainError> {
|
||||
let current_slot = chain.slot()?;
|
||||
let inner_chain = chain.clone();
|
||||
|
||||
// Use a blocking task to check if the block is an optimistic candidate. Interacting
|
||||
// with the `fork_choice` lock in an async task can block the core executor.
|
||||
chain
|
||||
.spawn_blocking_handle(
|
||||
move || {
|
||||
inner_chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.is_optimistic_candidate_block(
|
||||
current_slot,
|
||||
block_slot,
|
||||
&block_parent_root,
|
||||
&inner_chain.spec,
|
||||
)
|
||||
},
|
||||
"validate_merge_block_optimistic_candidate",
|
||||
)
|
||||
.await?
|
||||
.map_err(BeaconChainError::from)
|
||||
}
|
||||
|
||||
/// Validate the gossip block's execution_payload according to the checks described here:
|
||||
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#beacon_block
|
||||
pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
|
||||
@@ -243,35 +296,52 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
|
||||
/// Equivalent to the `get_execution_payload` function in the Validator Guide:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
|
||||
pub fn get_execution_payload<T: BeaconChainTypes, Payload: ExecPayload<T::EthSpec>>(
|
||||
chain: &BeaconChain<T>,
|
||||
pub fn get_execution_payload<
|
||||
T: BeaconChainTypes,
|
||||
Payload: ExecPayload<T::EthSpec> + Default + Send + 'static,
|
||||
>(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
proposer_index: u64,
|
||||
pubkey: Option<PublicKeyBytes>,
|
||||
) -> Result<Payload, BlockProductionError> {
|
||||
Ok(
|
||||
prepare_execution_payload_blocking::<T, Payload>(chain, state, proposer_index, pubkey)?
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
) -> Result<PreparePayloadHandle<Payload>, BlockProductionError> {
|
||||
// Compute all required values from the `state` now to avoid needing to pass it into a spawned
|
||||
// task.
|
||||
let spec = &chain.spec;
|
||||
let slot = state.slot();
|
||||
let current_epoch = state.current_epoch();
|
||||
let is_merge_transition_complete = is_merge_transition_complete(state);
|
||||
let timestamp = compute_timestamp_at_slot(state, spec).map_err(BeaconStateError::from)?;
|
||||
let random = *state.get_randao_mix(current_epoch)?;
|
||||
let latest_execution_payload_header_block_hash =
|
||||
state.latest_execution_payload_header()?.block_hash;
|
||||
|
||||
/// Wraps the async `prepare_execution_payload` function as a blocking task.
|
||||
pub fn prepare_execution_payload_blocking<T: BeaconChainTypes, Payload: ExecPayload<T::EthSpec>>(
|
||||
chain: &BeaconChain<T>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
proposer_index: u64,
|
||||
pubkey: Option<PublicKeyBytes>,
|
||||
) -> Result<Option<Payload>, BlockProductionError> {
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
|
||||
// Spawn a task to obtain the execution payload from the EL via a series of async calls. The
|
||||
// `join_handle` can be used to await the result of the function.
|
||||
let join_handle = chain
|
||||
.task_executor
|
||||
.clone()
|
||||
.spawn_handle(
|
||||
async move {
|
||||
prepare_execution_payload::<T, Payload>(
|
||||
&chain,
|
||||
slot,
|
||||
is_merge_transition_complete,
|
||||
timestamp,
|
||||
random,
|
||||
finalized_checkpoint,
|
||||
proposer_index,
|
||||
pubkey,
|
||||
latest_execution_payload_header_block_hash,
|
||||
)
|
||||
.await
|
||||
},
|
||||
"get_execution_payload",
|
||||
)
|
||||
.ok_or(BlockProductionError::ShuttingDown)?;
|
||||
|
||||
execution_layer
|
||||
.block_on_generic(|_| async {
|
||||
prepare_execution_payload::<T, Payload>(chain, state, proposer_index, pubkey).await
|
||||
})
|
||||
.map_err(BlockProductionError::BlockingFailed)?
|
||||
Ok(join_handle)
|
||||
}
|
||||
|
||||
/// Prepares an execution payload for inclusion in a block.
|
||||
@@ -288,25 +358,38 @@ pub fn prepare_execution_payload_blocking<T: BeaconChainTypes, Payload: ExecPayl
|
||||
/// Equivalent to the `prepare_execution_payload` function in the Validator Guide:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
|
||||
pub async fn prepare_execution_payload<T: BeaconChainTypes, Payload: ExecPayload<T::EthSpec>>(
|
||||
chain: &BeaconChain<T>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn prepare_execution_payload<T, Payload>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
slot: Slot,
|
||||
is_merge_transition_complete: bool,
|
||||
timestamp: u64,
|
||||
random: Hash256,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
proposer_index: u64,
|
||||
pubkey: Option<PublicKeyBytes>,
|
||||
) -> Result<Option<Payload>, BlockProductionError> {
|
||||
latest_execution_payload_header_block_hash: ExecutionBlockHash,
|
||||
) -> Result<Payload, BlockProductionError>
|
||||
where
|
||||
T: BeaconChainTypes,
|
||||
Payload: ExecPayload<T::EthSpec> + Default,
|
||||
{
|
||||
let current_epoch = slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
let spec = &chain.spec;
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
|
||||
|
||||
let parent_hash = if !is_merge_transition_complete(state) {
|
||||
let parent_hash = if !is_merge_transition_complete {
|
||||
let is_terminal_block_hash_set = spec.terminal_block_hash != ExecutionBlockHash::zero();
|
||||
let is_activation_epoch_reached =
|
||||
state.current_epoch() >= spec.terminal_block_hash_activation_epoch;
|
||||
current_epoch >= spec.terminal_block_hash_activation_epoch;
|
||||
|
||||
if is_terminal_block_hash_set && !is_activation_epoch_reached {
|
||||
return Ok(None);
|
||||
// Use the "empty" payload if there's a terminal block hash, but we haven't reached the
|
||||
// terminal block epoch yet.
|
||||
return Ok(<_>::default());
|
||||
}
|
||||
|
||||
let terminal_pow_block_hash = execution_layer
|
||||
@@ -317,36 +400,55 @@ pub async fn prepare_execution_payload<T: BeaconChainTypes, Payload: ExecPayload
|
||||
if let Some(terminal_pow_block_hash) = terminal_pow_block_hash {
|
||||
terminal_pow_block_hash
|
||||
} else {
|
||||
return Ok(None);
|
||||
// If the merge transition hasn't occurred yet and the EL hasn't found the terminal
|
||||
// block, return an "empty" payload.
|
||||
return Ok(<_>::default());
|
||||
}
|
||||
} else {
|
||||
state.latest_execution_payload_header()?.block_hash
|
||||
latest_execution_payload_header_block_hash
|
||||
};
|
||||
|
||||
let timestamp = compute_timestamp_at_slot(state, spec).map_err(BeaconStateError::from)?;
|
||||
let random = *state.get_randao_mix(state.current_epoch())?;
|
||||
let finalized_root = state.finalized_checkpoint().root;
|
||||
// Try to obtain the finalized proto block from fork choice.
|
||||
//
|
||||
// Use a blocking task to interact with the `fork_choice` lock otherwise we risk blocking the
|
||||
// core `tokio` executor.
|
||||
let inner_chain = chain.clone();
|
||||
let finalized_proto_block = chain
|
||||
.spawn_blocking_handle(
|
||||
move || {
|
||||
inner_chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&finalized_checkpoint.root)
|
||||
},
|
||||
"prepare_execution_payload_finalized_hash",
|
||||
)
|
||||
.await
|
||||
.map_err(BlockProductionError::BeaconChain)?;
|
||||
|
||||
// The finalized block hash is not included in the specification, however we provide this
|
||||
// parameter so that the execution layer can produce a payload id if one is not already known
|
||||
// (e.g., due to a recent reorg).
|
||||
let finalized_block_hash =
|
||||
if let Some(block) = chain.fork_choice.read().get_block(&finalized_root) {
|
||||
block.execution_status.block_hash()
|
||||
} else {
|
||||
chain
|
||||
.store
|
||||
.get_blinded_block(&finalized_root)
|
||||
.map_err(BlockProductionError::FailedToReadFinalizedBlock)?
|
||||
.ok_or(BlockProductionError::MissingFinalizedBlock(finalized_root))?
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.ok()
|
||||
.map(|ep| ep.block_hash())
|
||||
};
|
||||
let finalized_block_hash = if let Some(block) = finalized_proto_block {
|
||||
block.execution_status.block_hash()
|
||||
} else {
|
||||
chain
|
||||
.store
|
||||
.get_blinded_block(&finalized_checkpoint.root)
|
||||
.map_err(BlockProductionError::FailedToReadFinalizedBlock)?
|
||||
.ok_or(BlockProductionError::MissingFinalizedBlock(
|
||||
finalized_checkpoint.root,
|
||||
))?
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.ok()
|
||||
.map(|ep| ep.block_hash())
|
||||
};
|
||||
|
||||
// Note: the suggested_fee_recipient is stored in the `execution_layer`, it will add this parameter.
|
||||
//
|
||||
// This future is not executed here, it's up to the caller to await it.
|
||||
let execution_payload = execution_layer
|
||||
.get_payload::<Payload>(
|
||||
parent_hash,
|
||||
@@ -355,10 +457,10 @@ pub async fn prepare_execution_payload<T: BeaconChainTypes, Payload: ExecPayload
|
||||
finalized_block_hash.unwrap_or_else(ExecutionBlockHash::zero),
|
||||
proposer_index,
|
||||
pubkey,
|
||||
state.slot(),
|
||||
slot,
|
||||
)
|
||||
.await
|
||||
.map_err(BlockProductionError::GetPayloadFailed)?;
|
||||
|
||||
Ok(Some(execution_payload))
|
||||
Ok(execution_payload)
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
|
||||
head_block_root: Hash256,
|
||||
head_state: &BeaconState<E>,
|
||||
store: Arc<HotColdDB<E, Hot, Cold>>,
|
||||
current_slot: Option<Slot>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<ForkChoice<BeaconForkChoiceStore<E, Hot, Cold>, E>, String> {
|
||||
// Fetch finalized block.
|
||||
@@ -138,7 +139,7 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
|
||||
})?;
|
||||
let finalized_snapshot = BeaconSnapshot {
|
||||
beacon_block_root: finalized_block_root,
|
||||
beacon_block: finalized_block,
|
||||
beacon_block: Arc::new(finalized_block),
|
||||
beacon_state: finalized_state,
|
||||
};
|
||||
|
||||
@@ -149,6 +150,8 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
|
||||
finalized_block_root,
|
||||
&finalized_snapshot.beacon_block,
|
||||
&finalized_snapshot.beacon_state,
|
||||
current_slot,
|
||||
spec,
|
||||
)
|
||||
.map_err(|e| format!("Unable to reset fork choice for revert: {:?}", e))?;
|
||||
|
||||
@@ -180,11 +183,10 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
|
||||
// This scenario is so rare that it seems OK to double-verify some blocks.
|
||||
let payload_verification_status = PayloadVerificationStatus::Optimistic;
|
||||
|
||||
let (block, _) = block.deconstruct();
|
||||
fork_choice
|
||||
.on_block(
|
||||
block.slot(),
|
||||
&block,
|
||||
block.message(),
|
||||
block.canonical_root(),
|
||||
// Reward proposer boost. We are reinforcing the canonical chain.
|
||||
Duration::from_secs(0),
|
||||
|
||||
@@ -7,6 +7,7 @@ use state_processing::{
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use store::{chunked_vector::BlockRoots, AnchorInfo, ChunkWriter, KeyValueStore};
|
||||
use types::{Hash256, SignedBlindedBeaconBlock, Slot};
|
||||
@@ -58,7 +59,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// Return the number of blocks successfully imported.
|
||||
pub fn import_historical_block_batch(
|
||||
&self,
|
||||
blocks: Vec<SignedBlindedBeaconBlock<T::EthSpec>>,
|
||||
blocks: Vec<Arc<SignedBlindedBeaconBlock<T::EthSpec>>>,
|
||||
) -> Result<usize, Error> {
|
||||
let anchor_info = self
|
||||
.store
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod block_reward;
|
||||
mod block_times_cache;
|
||||
mod block_verification;
|
||||
pub mod builder;
|
||||
pub mod canonical_head;
|
||||
pub mod chain_config;
|
||||
mod early_attester_cache;
|
||||
mod errors;
|
||||
@@ -42,8 +43,8 @@ mod validator_pubkey_cache;
|
||||
|
||||
pub use self::beacon_chain::{
|
||||
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BeaconStore, ChainSegmentResult,
|
||||
ForkChoiceError, HeadInfo, HeadSafetyStatus, ProduceBlockVerification, StateSkipConfig,
|
||||
WhenSlotSkipped, INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
ForkChoiceError, ProduceBlockVerification, StateSkipConfig, WhenSlotSkipped,
|
||||
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
};
|
||||
pub use self::beacon_snapshot::BeaconSnapshot;
|
||||
pub use self::chain_config::ChainConfig;
|
||||
@@ -52,8 +53,10 @@ pub use self::historical_blocks::HistoricalBlockError;
|
||||
pub use attestation_verification::Error as AttestationError;
|
||||
pub use beacon_fork_choice_store::{BeaconForkChoiceStore, Error as ForkChoiceStoreError};
|
||||
pub use block_verification::{BlockError, ExecutionPayloadError, GossipVerifiedBlock};
|
||||
pub use canonical_head::{CachedHead, CanonicalHead, CanonicalHeadRwLock};
|
||||
pub use eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
||||
pub use events::ServerSentEventHandler;
|
||||
pub use fork_choice::ExecutionStatus;
|
||||
pub use metrics::scrape_for_metrics;
|
||||
pub use parking_lot;
|
||||
pub use slot_clock;
|
||||
|
||||
@@ -51,9 +51,7 @@ async fn proposer_prep_service<T: BeaconChainTypes>(
|
||||
executor.spawn(
|
||||
async move {
|
||||
if let Ok(current_slot) = inner_chain.slot() {
|
||||
if let Err(e) = inner_chain
|
||||
.prepare_beacon_proposer_async(current_slot)
|
||||
.await
|
||||
if let Err(e) = inner_chain.prepare_beacon_proposer(current_slot).await
|
||||
{
|
||||
error!(
|
||||
inner_chain.log,
|
||||
|
||||
@@ -7,6 +7,7 @@ mod types;
|
||||
|
||||
use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY};
|
||||
use crate::persisted_fork_choice::{PersistedForkChoiceV1, PersistedForkChoiceV7};
|
||||
use crate::types::ChainSpec;
|
||||
use slog::{warn, Logger};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -21,6 +22,7 @@ pub fn migrate_schema<T: BeaconChainTypes>(
|
||||
from: SchemaVersion,
|
||||
to: SchemaVersion,
|
||||
log: Logger,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), StoreError> {
|
||||
match (from, to) {
|
||||
// Migrating from the current schema version to iself is always OK, a no-op.
|
||||
@@ -28,8 +30,8 @@ pub fn migrate_schema<T: BeaconChainTypes>(
|
||||
// Upgrade across multiple versions by recursively migrating one step at a time.
|
||||
(_, _) if from.as_u64() + 1 < to.as_u64() => {
|
||||
let next = SchemaVersion(from.as_u64() + 1);
|
||||
migrate_schema::<T>(db.clone(), datadir, from, next, log.clone())?;
|
||||
migrate_schema::<T>(db, datadir, next, to, log)
|
||||
migrate_schema::<T>(db.clone(), datadir, from, next, log.clone(), spec)?;
|
||||
migrate_schema::<T>(db, datadir, next, to, log, spec)
|
||||
}
|
||||
|
||||
//
|
||||
@@ -89,6 +91,7 @@ pub fn migrate_schema<T: BeaconChainTypes>(
|
||||
migration_schema_v7::update_with_reinitialized_fork_choice::<T>(
|
||||
&mut persisted_fork_choice_v7,
|
||||
db.clone(),
|
||||
spec,
|
||||
)
|
||||
.map_err(StoreError::SchemaMigrationError)?;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ use crate::beacon_chain::BeaconChainTypes;
|
||||
use crate::beacon_fork_choice_store::{PersistedForkChoiceStoreV1, PersistedForkChoiceStoreV7};
|
||||
use crate::persisted_fork_choice::{PersistedForkChoiceV1, PersistedForkChoiceV7};
|
||||
use crate::schema_change::types::{ProtoNodeV6, SszContainerV6, SszContainerV7};
|
||||
use crate::types::{Checkpoint, Epoch, Hash256};
|
||||
use crate::types::{EthSpec, Slot};
|
||||
use crate::types::{ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, Slot};
|
||||
use crate::{BeaconForkChoiceStore, BeaconSnapshot};
|
||||
use fork_choice::ForkChoice;
|
||||
use proto_array::{core::ProtoNode, core::SszContainer, ProtoArrayForkChoice};
|
||||
@@ -25,6 +24,7 @@ four_byte_option_impl!(four_byte_option_usize, usize);
|
||||
pub(crate) fn update_with_reinitialized_fork_choice<T: BeaconChainTypes>(
|
||||
persisted_fork_choice: &mut PersistedForkChoiceV7,
|
||||
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), String> {
|
||||
let anchor_block_root = persisted_fork_choice
|
||||
.fork_choice_store
|
||||
@@ -39,7 +39,7 @@ pub(crate) fn update_with_reinitialized_fork_choice<T: BeaconChainTypes>(
|
||||
.map_err(|e| format!("{:?}", e))?
|
||||
.ok_or_else(|| "Missing anchor beacon state".to_string())?;
|
||||
let snapshot = BeaconSnapshot {
|
||||
beacon_block: anchor_block,
|
||||
beacon_block: Arc::new(anchor_block),
|
||||
beacon_block_root: anchor_block_root,
|
||||
beacon_state: anchor_state,
|
||||
};
|
||||
@@ -49,6 +49,10 @@ pub(crate) fn update_with_reinitialized_fork_choice<T: BeaconChainTypes>(
|
||||
anchor_block_root,
|
||||
&snapshot.beacon_block,
|
||||
&snapshot.beacon_state,
|
||||
// Don't provide the current slot here, just use what's in the store. We don't need to know
|
||||
// the head here, plus it's nice to avoid mutating fork choice during this process.
|
||||
None,
|
||||
spec,
|
||||
)
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
persisted_fork_choice.fork_choice = fork_choice.to_persisted();
|
||||
|
||||
@@ -47,6 +47,12 @@ impl ShufflingCache {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ShufflingCache {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the shuffling IDs for a beacon block.
|
||||
pub struct BlockShufflingIds {
|
||||
pub current: AttestationShufflingId,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::BeaconSnapshot;
|
||||
use itertools::process_results;
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{
|
||||
beacon_state::CloneConfig, BeaconState, BlindedPayload, ChainSpec, Epoch, EthSpec, Hash256,
|
||||
@@ -33,7 +34,7 @@ impl<T: EthSpec> From<BeaconSnapshot<T>> for PreProcessingSnapshot<T> {
|
||||
Self {
|
||||
pre_state: snapshot.beacon_state,
|
||||
beacon_state_root,
|
||||
beacon_block: snapshot.beacon_block.into(),
|
||||
beacon_block: snapshot.beacon_block.clone_as_blinded(),
|
||||
beacon_block_root: snapshot.beacon_block_root,
|
||||
}
|
||||
}
|
||||
@@ -63,7 +64,7 @@ impl<T: EthSpec> CacheItem<T> {
|
||||
Some(self.beacon_block.state_root()).filter(|_| self.pre_state.is_none());
|
||||
|
||||
PreProcessingSnapshot {
|
||||
beacon_block: self.beacon_block.into(),
|
||||
beacon_block: self.beacon_block.clone_as_blinded(),
|
||||
beacon_block_root: self.beacon_block_root,
|
||||
pre_state: self.pre_state.unwrap_or(self.beacon_state),
|
||||
beacon_state_root,
|
||||
@@ -76,7 +77,7 @@ impl<T: EthSpec> CacheItem<T> {
|
||||
Some(self.beacon_block.state_root()).filter(|_| self.pre_state.is_none());
|
||||
|
||||
PreProcessingSnapshot {
|
||||
beacon_block: self.beacon_block.clone().into(),
|
||||
beacon_block: self.beacon_block.clone_as_blinded(),
|
||||
beacon_block_root: self.beacon_block_root,
|
||||
pre_state: self
|
||||
.pre_state
|
||||
@@ -116,7 +117,7 @@ pub enum StateAdvance<T: EthSpec> {
|
||||
|
||||
/// The item stored in the `SnapshotCache`.
|
||||
pub struct CacheItem<T: EthSpec> {
|
||||
beacon_block: SignedBeaconBlock<T>,
|
||||
beacon_block: Arc<SignedBeaconBlock<T>>,
|
||||
beacon_block_root: Hash256,
|
||||
/// This state is equivalent to `self.beacon_block.state_root()`.
|
||||
beacon_state: BeaconState<T>,
|
||||
@@ -185,7 +186,7 @@ impl<T: EthSpec> SnapshotCache<T> {
|
||||
) {
|
||||
let parent_root = snapshot.beacon_block.message().parent_root();
|
||||
let item = CacheItem {
|
||||
beacon_block: snapshot.beacon_block,
|
||||
beacon_block: snapshot.beacon_block.clone(),
|
||||
beacon_block_root: snapshot.beacon_block_root,
|
||||
beacon_state: snapshot.beacon_state,
|
||||
pre_state,
|
||||
@@ -384,7 +385,7 @@ mod test {
|
||||
fn get_snapshot(i: u64) -> BeaconSnapshot<MainnetEthSpec> {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
|
||||
let beacon_state = get_harness().chain.head_beacon_state().unwrap();
|
||||
let beacon_state = get_harness().chain.head_beacon_state_cloned();
|
||||
|
||||
let signed_beacon_block = SignedBeaconBlock::from_block(
|
||||
BeaconBlock::empty(&spec),
|
||||
@@ -395,7 +396,7 @@ mod test {
|
||||
|
||||
BeaconSnapshot {
|
||||
beacon_state,
|
||||
beacon_block: signed_beacon_block,
|
||||
beacon_block: Arc::new(signed_beacon_block),
|
||||
beacon_block_root: Hash256::from_low_u64_be(i),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,16 +213,14 @@ async fn state_advance_timer<T: BeaconChainTypes>(
|
||||
let log = log.clone();
|
||||
let beacon_chain = beacon_chain.clone();
|
||||
let next_slot = current_slot + 1;
|
||||
executor.spawn_blocking(
|
||||
move || {
|
||||
executor.spawn(
|
||||
async move {
|
||||
// Don't run fork choice during sync.
|
||||
if beacon_chain.best_slot().map_or(true, |head_slot| {
|
||||
head_slot + MAX_FORK_CHOICE_DISTANCE < current_slot
|
||||
}) {
|
||||
if beacon_chain.best_slot() + MAX_FORK_CHOICE_DISTANCE < current_slot {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = beacon_chain.fork_choice_at_slot(next_slot) {
|
||||
if let Err(e) = beacon_chain.recompute_head_at_slot(next_slot).await {
|
||||
warn!(
|
||||
log,
|
||||
"Error updating fork choice for next slot";
|
||||
@@ -231,17 +229,24 @@ async fn state_advance_timer<T: BeaconChainTypes>(
|
||||
);
|
||||
}
|
||||
|
||||
// Signal block proposal for the next slot (if it happens to be waiting).
|
||||
if let Some(tx) = &beacon_chain.fork_choice_signal_tx {
|
||||
if let Err(e) = tx.notify_fork_choice_complete(next_slot) {
|
||||
warn!(
|
||||
log,
|
||||
"Error signalling fork choice waiter";
|
||||
"error" => ?e,
|
||||
"slot" => next_slot,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Use a blocking task to avoid blocking the core executor whilst waiting for locks
|
||||
// in `ForkChoiceSignalTx`.
|
||||
beacon_chain.task_executor.clone().spawn_blocking(
|
||||
move || {
|
||||
// Signal block proposal for the next slot (if it happens to be waiting).
|
||||
if let Some(tx) = &beacon_chain.fork_choice_signal_tx {
|
||||
if let Err(e) = tx.notify_fork_choice_complete(next_slot) {
|
||||
warn!(
|
||||
log,
|
||||
"Error signalling fork choice waiter";
|
||||
"error" => ?e,
|
||||
"slot" => next_slot,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
"fork_choice_advance_signal_tx",
|
||||
);
|
||||
},
|
||||
"fork_choice_advance",
|
||||
);
|
||||
@@ -264,7 +269,7 @@ fn advance_head<T: BeaconChainTypes>(
|
||||
//
|
||||
// Fork-choice is not run *before* this function to avoid unnecessary calls whilst syncing.
|
||||
{
|
||||
let head_slot = beacon_chain.head_info()?.slot;
|
||||
let head_slot = beacon_chain.best_slot();
|
||||
|
||||
// Don't run this when syncing or if lagging too far behind.
|
||||
if head_slot + MAX_ADVANCE_DISTANCE < current_slot {
|
||||
@@ -275,7 +280,7 @@ fn advance_head<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
let head_root = beacon_chain.head_info()?.block_root;
|
||||
let head_root = beacon_chain.head_beacon_block_root();
|
||||
|
||||
let (head_slot, head_state_root, mut state) = match beacon_chain
|
||||
.snapshot_cache
|
||||
|
||||
@@ -515,13 +515,38 @@ where
|
||||
}
|
||||
|
||||
pub fn get_current_state(&self) -> BeaconState<E> {
|
||||
self.chain.head().unwrap().beacon_state
|
||||
self.chain.head_beacon_state_cloned()
|
||||
}
|
||||
|
||||
pub fn get_current_state_and_root(&self) -> (BeaconState<E>, Hash256) {
|
||||
let head = self.chain.head().unwrap();
|
||||
let head = self.chain.head_snapshot();
|
||||
let state_root = head.beacon_state_root();
|
||||
(head.beacon_state, state_root)
|
||||
(
|
||||
head.beacon_state.clone_with_only_committee_caches(),
|
||||
state_root,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn head_slot(&self) -> Slot {
|
||||
self.chain.canonical_head.cached_head().head_slot()
|
||||
}
|
||||
|
||||
pub fn head_block_root(&self) -> Hash256 {
|
||||
self.chain.canonical_head.cached_head().head_block_root()
|
||||
}
|
||||
|
||||
pub fn finalized_checkpoint(&self) -> Checkpoint {
|
||||
self.chain
|
||||
.canonical_head
|
||||
.cached_head()
|
||||
.finalized_checkpoint()
|
||||
}
|
||||
|
||||
pub fn justified_checkpoint(&self) -> Checkpoint {
|
||||
self.chain
|
||||
.canonical_head
|
||||
.cached_head()
|
||||
.justified_checkpoint()
|
||||
}
|
||||
|
||||
pub fn get_current_slot(&self) -> Slot {
|
||||
@@ -565,7 +590,7 @@ where
|
||||
state.get_block_root(slot).unwrap() == state.get_block_root(slot - 1).unwrap()
|
||||
}
|
||||
|
||||
pub fn make_block(
|
||||
pub async fn make_block(
|
||||
&self,
|
||||
mut state: BeaconState<E>,
|
||||
slot: Slot,
|
||||
@@ -599,6 +624,7 @@ where
|
||||
Some(graffiti),
|
||||
ProduceBlockVerification::VerifyRandao,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let signed_block = block.sign(
|
||||
@@ -613,7 +639,7 @@ where
|
||||
|
||||
/// Useful for the `per_block_processing` tests. Creates a block, and returns the state after
|
||||
/// caches are built but before the generated block is processed.
|
||||
pub fn make_block_return_pre_state(
|
||||
pub async fn make_block_return_pre_state(
|
||||
&self,
|
||||
mut state: BeaconState<E>,
|
||||
slot: Slot,
|
||||
@@ -649,6 +675,7 @@ where
|
||||
Some(graffiti),
|
||||
ProduceBlockVerification::VerifyRandao,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let signed_block = block.sign(
|
||||
@@ -1098,11 +1125,11 @@ where
|
||||
let mut attestation_2 = attestation_1.clone();
|
||||
attestation_2.data.index += 1;
|
||||
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
for attestation in &mut [&mut attestation_1, &mut attestation_2] {
|
||||
for &i in &attestation.attesting_indices {
|
||||
let sk = &self.validator_keypairs[i as usize].sk;
|
||||
|
||||
let fork = self.chain.head_info().unwrap().fork;
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
let domain = self.chain.spec.get_domain(
|
||||
@@ -1156,11 +1183,11 @@ where
|
||||
|
||||
attestation_2.data.index += 1;
|
||||
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
for attestation in &mut [&mut attestation_1, &mut attestation_2] {
|
||||
for &i in &attestation.attesting_indices {
|
||||
let sk = &self.validator_keypairs[i as usize].sk;
|
||||
|
||||
let fork = self.chain.head_info().unwrap().fork;
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
let domain = self.chain.spec.get_domain(
|
||||
@@ -1182,19 +1209,14 @@ where
|
||||
}
|
||||
|
||||
pub fn make_proposer_slashing(&self, validator_index: u64) -> ProposerSlashing {
|
||||
let mut block_header_1 = self
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.unwrap()
|
||||
.message()
|
||||
.block_header();
|
||||
let mut block_header_1 = self.chain.head_beacon_block().message().block_header();
|
||||
block_header_1.proposer_index = validator_index;
|
||||
|
||||
let mut block_header_2 = block_header_1.clone();
|
||||
block_header_2.state_root = Hash256::zero();
|
||||
|
||||
let sk = &self.validator_keypairs[validator_index as usize].sk;
|
||||
let fork = self.chain.head_info().unwrap().fork;
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
let mut signed_block_headers = vec![block_header_1, block_header_2]
|
||||
@@ -1212,7 +1234,7 @@ where
|
||||
|
||||
pub fn make_voluntary_exit(&self, validator_index: u64, epoch: Epoch) -> SignedVoluntaryExit {
|
||||
let sk = &self.validator_keypairs[validator_index as usize].sk;
|
||||
let fork = self.chain.head_info().unwrap().fork;
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
VoluntaryExit {
|
||||
@@ -1235,7 +1257,7 @@ where
|
||||
/// Create a new block, apply `block_modifier` to it, sign it and return it.
|
||||
///
|
||||
/// The state returned is a pre-block state at the same slot as the produced block.
|
||||
pub fn make_block_with_modifier(
|
||||
pub async fn make_block_with_modifier(
|
||||
&self,
|
||||
state: BeaconState<E>,
|
||||
slot: Slot,
|
||||
@@ -1244,7 +1266,7 @@ where
|
||||
assert_ne!(slot, 0, "can't produce a block at slot 0");
|
||||
assert!(slot >= state.slot());
|
||||
|
||||
let (block, state) = self.make_block_return_pre_state(state, slot);
|
||||
let (block, state) = self.make_block_return_pre_state(state, slot).await;
|
||||
let (mut block, _) = block.deconstruct();
|
||||
|
||||
block_modifier(&mut block);
|
||||
@@ -1332,23 +1354,25 @@ where
|
||||
(deposits, state)
|
||||
}
|
||||
|
||||
pub fn process_block(
|
||||
pub async fn process_block(
|
||||
&self,
|
||||
slot: Slot,
|
||||
block: SignedBeaconBlock<E>,
|
||||
) -> Result<SignedBeaconBlockHash, BlockError<E>> {
|
||||
self.set_current_slot(slot);
|
||||
let block_hash: SignedBeaconBlockHash = self.chain.process_block(block)?.into();
|
||||
self.chain.fork_choice()?;
|
||||
let block_hash: SignedBeaconBlockHash =
|
||||
self.chain.process_block(Arc::new(block)).await?.into();
|
||||
self.chain.recompute_head_at_current_slot().await?;
|
||||
Ok(block_hash)
|
||||
}
|
||||
|
||||
pub fn process_block_result(
|
||||
pub async fn process_block_result(
|
||||
&self,
|
||||
block: SignedBeaconBlock<E>,
|
||||
) -> Result<SignedBeaconBlockHash, BlockError<E>> {
|
||||
let block_hash: SignedBeaconBlockHash = self.chain.process_block(block)?.into();
|
||||
self.chain.fork_choice().unwrap();
|
||||
let block_hash: SignedBeaconBlockHash =
|
||||
self.chain.process_block(Arc::new(block)).await?.into();
|
||||
self.chain.recompute_head_at_current_slot().await?;
|
||||
Ok(block_hash)
|
||||
}
|
||||
|
||||
@@ -1403,14 +1427,14 @@ where
|
||||
self.chain.slot_clock.set_slot(slot.into());
|
||||
}
|
||||
|
||||
pub fn add_block_at_slot(
|
||||
pub async fn add_block_at_slot(
|
||||
&self,
|
||||
slot: Slot,
|
||||
state: BeaconState<E>,
|
||||
) -> Result<(SignedBeaconBlockHash, SignedBeaconBlock<E>, BeaconState<E>), BlockError<E>> {
|
||||
self.set_current_slot(slot);
|
||||
let (block, new_state) = self.make_block(state, slot);
|
||||
let block_hash = self.process_block(slot, block.clone())?;
|
||||
let (block, new_state) = self.make_block(state, slot).await;
|
||||
let block_hash = self.process_block(slot, block.clone()).await?;
|
||||
Ok((block_hash, block, new_state))
|
||||
}
|
||||
|
||||
@@ -1427,19 +1451,19 @@ where
|
||||
self.process_attestations(attestations);
|
||||
}
|
||||
|
||||
pub fn add_attested_block_at_slot(
|
||||
pub async fn add_attested_block_at_slot(
|
||||
&self,
|
||||
slot: Slot,
|
||||
state: BeaconState<E>,
|
||||
state_root: Hash256,
|
||||
validators: &[usize],
|
||||
) -> Result<(SignedBeaconBlockHash, BeaconState<E>), BlockError<E>> {
|
||||
let (block_hash, block, state) = self.add_block_at_slot(slot, state)?;
|
||||
let (block_hash, block, state) = self.add_block_at_slot(slot, state).await?;
|
||||
self.attest_block(&state, state_root, block_hash, &block, validators);
|
||||
Ok((block_hash, state))
|
||||
}
|
||||
|
||||
pub fn add_attested_blocks_at_slots(
|
||||
pub async fn add_attested_blocks_at_slots(
|
||||
&self,
|
||||
state: BeaconState<E>,
|
||||
state_root: Hash256,
|
||||
@@ -1448,9 +1472,10 @@ where
|
||||
) -> AddBlocksResult<E> {
|
||||
assert!(!slots.is_empty());
|
||||
self.add_attested_blocks_at_slots_given_lbh(state, state_root, slots, validators, None)
|
||||
.await
|
||||
}
|
||||
|
||||
fn add_attested_blocks_at_slots_given_lbh(
|
||||
async fn add_attested_blocks_at_slots_given_lbh(
|
||||
&self,
|
||||
mut state: BeaconState<E>,
|
||||
state_root: Hash256,
|
||||
@@ -1467,6 +1492,7 @@ where
|
||||
for slot in slots {
|
||||
let (block_hash, new_state) = self
|
||||
.add_attested_block_at_slot(*slot, state, state_root, validators)
|
||||
.await
|
||||
.unwrap();
|
||||
state = new_state;
|
||||
block_hash_from_slot.insert(*slot, block_hash);
|
||||
@@ -1488,7 +1514,7 @@ where
|
||||
/// epoch at a time.
|
||||
///
|
||||
/// Chains is a vec of `(state, slots, validators)` tuples.
|
||||
pub fn add_blocks_on_multiple_chains(
|
||||
pub async fn add_blocks_on_multiple_chains(
|
||||
&self,
|
||||
chains: Vec<(BeaconState<E>, Vec<Slot>, Vec<usize>)>,
|
||||
) -> Vec<AddBlocksResult<E>> {
|
||||
@@ -1547,7 +1573,8 @@ where
|
||||
&epoch_slots,
|
||||
&validators,
|
||||
Some(head_block),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
block_hashes.extend(new_block_hashes);
|
||||
state_hashes.extend(new_state_hashes);
|
||||
@@ -1596,18 +1623,18 @@ where
|
||||
/// Deprecated: Use make_block() instead
|
||||
///
|
||||
/// Returns a newly created block, signed by the proposer for the given slot.
|
||||
pub fn build_block(
|
||||
pub async fn build_block(
|
||||
&self,
|
||||
state: BeaconState<E>,
|
||||
slot: Slot,
|
||||
_block_strategy: BlockStrategy,
|
||||
) -> (SignedBeaconBlock<E>, BeaconState<E>) {
|
||||
self.make_block(state, slot)
|
||||
self.make_block(state, slot).await
|
||||
}
|
||||
|
||||
/// Uses `Self::extend_chain` to build the chain out to the `target_slot`.
|
||||
pub fn extend_to_slot(&self, target_slot: Slot) -> Hash256 {
|
||||
if self.chain.slot().unwrap() == self.chain.head_info().unwrap().slot {
|
||||
pub async fn extend_to_slot(&self, target_slot: Slot) -> Hash256 {
|
||||
if self.chain.slot().unwrap() == self.chain.canonical_head.cached_head().head_slot() {
|
||||
self.advance_slot();
|
||||
}
|
||||
|
||||
@@ -1618,7 +1645,7 @@ where
|
||||
.checked_add(1)
|
||||
.unwrap();
|
||||
|
||||
self.extend_slots(num_slots)
|
||||
self.extend_slots(num_slots).await
|
||||
}
|
||||
|
||||
/// Uses `Self::extend_chain` to `num_slots` blocks.
|
||||
@@ -1627,8 +1654,8 @@ where
|
||||
///
|
||||
/// - BlockStrategy::OnCanonicalHead,
|
||||
/// - AttestationStrategy::AllValidators,
|
||||
pub fn extend_slots(&self, num_slots: usize) -> Hash256 {
|
||||
if self.chain.slot().unwrap() == self.chain.head_info().unwrap().slot {
|
||||
pub async fn extend_slots(&self, num_slots: usize) -> Hash256 {
|
||||
if self.chain.slot().unwrap() == self.chain.canonical_head.cached_head().head_slot() {
|
||||
self.advance_slot();
|
||||
}
|
||||
|
||||
@@ -1637,6 +1664,7 @@ where
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Deprecated: Use add_attested_blocks_at_slots() instead
|
||||
@@ -1650,7 +1678,7 @@ where
|
||||
///
|
||||
/// The `attestation_strategy` dictates which validators will attest to the newly created
|
||||
/// blocks.
|
||||
pub fn extend_chain(
|
||||
pub async fn extend_chain(
|
||||
&self,
|
||||
num_blocks: usize,
|
||||
block_strategy: BlockStrategy,
|
||||
@@ -1685,8 +1713,9 @@ where
|
||||
AttestationStrategy::SomeValidators(vals) => vals,
|
||||
};
|
||||
let state_root = state.update_tree_hash_cache().unwrap();
|
||||
let (_, _, last_produced_block_hash, _) =
|
||||
self.add_attested_blocks_at_slots(state, state_root, &slots, &validators);
|
||||
let (_, _, last_produced_block_hash, _) = self
|
||||
.add_attested_blocks_at_slots(state, state_root, &slots, &validators)
|
||||
.await;
|
||||
last_produced_block_hash.into()
|
||||
}
|
||||
|
||||
@@ -1700,41 +1729,40 @@ where
|
||||
/// then built `faulty_fork_blocks`.
|
||||
///
|
||||
/// Returns `(honest_head, faulty_head)`, the roots of the blocks at the top of each chain.
|
||||
pub fn generate_two_forks_by_skipping_a_block(
|
||||
pub async fn generate_two_forks_by_skipping_a_block(
|
||||
&self,
|
||||
honest_validators: &[usize],
|
||||
faulty_validators: &[usize],
|
||||
honest_fork_blocks: usize,
|
||||
faulty_fork_blocks: usize,
|
||||
) -> (Hash256, Hash256) {
|
||||
let initial_head_slot = self
|
||||
.chain
|
||||
.head()
|
||||
.expect("should get head")
|
||||
.beacon_block
|
||||
.slot();
|
||||
let initial_head_slot = self.chain.head_snapshot().beacon_block.slot();
|
||||
|
||||
// Move to the next slot so we may produce some more blocks on the head.
|
||||
self.advance_slot();
|
||||
|
||||
// Extend the chain with blocks where only honest validators agree.
|
||||
let honest_head = self.extend_chain(
|
||||
honest_fork_blocks,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::SomeValidators(honest_validators.to_vec()),
|
||||
);
|
||||
let honest_head = self
|
||||
.extend_chain(
|
||||
honest_fork_blocks,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::SomeValidators(honest_validators.to_vec()),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Go back to the last block where all agreed, and build blocks upon it where only faulty nodes
|
||||
// agree.
|
||||
let faulty_head = self.extend_chain(
|
||||
faulty_fork_blocks,
|
||||
BlockStrategy::ForkCanonicalChainAt {
|
||||
previous_slot: initial_head_slot,
|
||||
// `initial_head_slot + 2` means one slot is skipped.
|
||||
first_slot: initial_head_slot + 2,
|
||||
},
|
||||
AttestationStrategy::SomeValidators(faulty_validators.to_vec()),
|
||||
);
|
||||
let faulty_head = self
|
||||
.extend_chain(
|
||||
faulty_fork_blocks,
|
||||
BlockStrategy::ForkCanonicalChainAt {
|
||||
previous_slot: initial_head_slot,
|
||||
// `initial_head_slot + 2` means one slot is skipped.
|
||||
first_slot: initial_head_slot + 2,
|
||||
},
|
||||
AttestationStrategy::SomeValidators(faulty_validators.to_vec()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_ne!(honest_head, faulty_head, "forks should be distinct");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user