mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-27 01:33:33 +00:00
Gloas spec v1.7.0-alpha.5 and beacon_chain tests (#8998)
Fix database pruning post-Gloas - Fix DB pruning logic (and state summaries DAG) - Get the `beacon_chain` tests running with `FORK_NAME=gloas` 🎉 Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com> Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com> Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com> Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
use crate::{
|
||||
BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError,
|
||||
VerifyBlockRoot, VerifySignatures,
|
||||
envelope_processing::{
|
||||
EnvelopeProcessingError, VerifyStateRoot, process_execution_payload_envelope,
|
||||
},
|
||||
per_block_processing,
|
||||
per_epoch_processing::EpochProcessingSummary,
|
||||
VerifyBlockRoot, per_block_processing, per_epoch_processing::EpochProcessingSummary,
|
||||
per_slot_processing,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@@ -13,7 +8,7 @@ use std::iter::Peekable;
|
||||
use std::marker::PhantomData;
|
||||
use types::{
|
||||
BeaconState, BeaconStateError, BlindedPayload, ChainSpec, EthSpec, Hash256, SignedBeaconBlock,
|
||||
SignedExecutionPayloadEnvelope, Slot, execution::StatePayloadStatus,
|
||||
Slot,
|
||||
};
|
||||
|
||||
pub type PreBlockHook<'a, E, Error> = Box<
|
||||
@@ -29,7 +24,7 @@ pub type PostSlotHook<'a, E, Error> = Box<
|
||||
>;
|
||||
pub type StateRootIterDefault<Error> = std::iter::Empty<Result<(Hash256, Slot), Error>>;
|
||||
|
||||
/// Efficiently apply blocks and payloads to a state while configuring various parameters.
|
||||
/// Efficiently apply blocks to a state while configuring various parameters.
|
||||
///
|
||||
/// Usage follows a builder pattern.
|
||||
pub struct BlockReplayer<
|
||||
@@ -46,21 +41,8 @@ pub struct BlockReplayer<
|
||||
post_block_hook: Option<PostBlockHook<'a, Spec, Error>>,
|
||||
pre_slot_hook: Option<PreSlotHook<'a, Spec, Error>>,
|
||||
post_slot_hook: Option<PostSlotHook<'a, Spec, Error>>,
|
||||
/// Iterator over state roots for all *block* states.
|
||||
///
|
||||
/// Pre-Gloas, this is all states. Post-Gloas, this is *just* the states corresponding to beacon
|
||||
/// blocks. For states corresponding to payloads, we read the state root from the payload
|
||||
/// envelope.
|
||||
// TODO(gloas): this concept might need adjusting when we implement the cold DB.
|
||||
pub(crate) state_root_iter: Option<Peekable<StateRootIter>>,
|
||||
state_root_miss: bool,
|
||||
/// The payload status of the state desired as the end result of block replay.
|
||||
///
|
||||
/// This dictates whether a payload should be applied after applying the last block.
|
||||
///
|
||||
/// Prior to Gloas, this should always be set to `StatePayloadStatus::Pending` to indicate
|
||||
/// that no envelope needs to be applied.
|
||||
desired_state_payload_status: StatePayloadStatus,
|
||||
_phantom: PhantomData<Error>,
|
||||
}
|
||||
|
||||
@@ -68,12 +50,7 @@ pub struct BlockReplayer<
|
||||
pub enum BlockReplayError {
|
||||
SlotProcessing(SlotProcessingError),
|
||||
BlockProcessing(BlockProcessingError),
|
||||
EnvelopeProcessing(EnvelopeProcessingError),
|
||||
BeaconState(BeaconStateError),
|
||||
/// A payload envelope for this `slot` was required but not provided.
|
||||
MissingPayloadEnvelope {
|
||||
slot: Slot,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<SlotProcessingError> for BlockReplayError {
|
||||
@@ -88,12 +65,6 @@ impl From<BlockProcessingError> for BlockReplayError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EnvelopeProcessingError> for BlockReplayError {
|
||||
fn from(e: EnvelopeProcessingError) -> Self {
|
||||
Self::EnvelopeProcessing(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for BlockReplayError {
|
||||
fn from(e: BeaconStateError) -> Self {
|
||||
Self::BeaconState(e)
|
||||
@@ -125,7 +96,6 @@ where
|
||||
post_slot_hook: None,
|
||||
state_root_iter: None,
|
||||
state_root_miss: false,
|
||||
desired_state_payload_status: StatePayloadStatus::Pending,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -191,14 +161,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the desired payload status of the state reached by replay.
|
||||
///
|
||||
/// This determines whether to apply a payload after applying the last block.
|
||||
pub fn desired_state_payload_status(mut self, payload_status: StatePayloadStatus) -> Self {
|
||||
self.desired_state_payload_status = payload_status;
|
||||
self
|
||||
}
|
||||
|
||||
/// Compute the state root for `self.state` as efficiently as possible.
|
||||
///
|
||||
/// This function MUST only be called when `self.state` is a post-state, i.e. it MUST not be
|
||||
@@ -246,38 +208,6 @@ where
|
||||
Ok(state_root)
|
||||
}
|
||||
|
||||
/// Apply an execution payload envelope to `self.state`.
|
||||
///
|
||||
/// The `block_state_root` MUST be the `state_root` of the most recently applied block.
|
||||
///
|
||||
/// Returns the `state_root` of `self.state` after payload application.
|
||||
fn apply_payload_envelope(
|
||||
&mut self,
|
||||
envelope: &SignedExecutionPayloadEnvelope<E>,
|
||||
block_state_root: Hash256,
|
||||
) -> Result<Hash256, Error> {
|
||||
// TODO(gloas): bulk signature verification could be relevant here?
|
||||
let verify_payload_signatures =
|
||||
if let BlockSignatureStrategy::NoVerification = self.block_sig_strategy {
|
||||
VerifySignatures::False
|
||||
} else {
|
||||
VerifySignatures::True
|
||||
};
|
||||
// TODO(gloas): state root verif enabled during initial prototyping
|
||||
let verify_state_root = VerifyStateRoot::True;
|
||||
process_execution_payload_envelope(
|
||||
&mut self.state,
|
||||
Some(block_state_root),
|
||||
envelope,
|
||||
verify_payload_signatures,
|
||||
verify_state_root,
|
||||
self.spec,
|
||||
)
|
||||
.map_err(BlockReplayError::from)?;
|
||||
|
||||
Ok(envelope.message.state_root)
|
||||
}
|
||||
|
||||
/// Apply `blocks` atop `self.state`, taking care of slot processing.
|
||||
///
|
||||
/// If `target_slot` is provided then the state will be advanced through to `target_slot`
|
||||
@@ -285,21 +215,8 @@ where
|
||||
pub fn apply_blocks(
|
||||
mut self,
|
||||
blocks: Vec<SignedBeaconBlock<E, BlindedPayload<E>>>,
|
||||
payload_envelopes: Vec<SignedExecutionPayloadEnvelope<E>>,
|
||||
target_slot: Option<Slot>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut envelopes_iter = payload_envelopes.into_iter();
|
||||
|
||||
let mut next_envelope_at_slot = |slot| {
|
||||
if let Some(envelope) = envelopes_iter.next()
|
||||
&& envelope.message.slot == slot
|
||||
{
|
||||
Ok(envelope)
|
||||
} else {
|
||||
Err(BlockReplayError::MissingPayloadEnvelope { slot })
|
||||
}
|
||||
};
|
||||
|
||||
for (i, block) in blocks.iter().enumerate() {
|
||||
// Allow one additional block at the start which is only used for its state root.
|
||||
if i == 0 && block.slot() <= self.state.slot() {
|
||||
@@ -307,36 +224,7 @@ where
|
||||
}
|
||||
|
||||
while self.state.slot() < block.slot() {
|
||||
let mut state_root = self.get_state_root(&blocks, i)?;
|
||||
|
||||
// Apply the payload for the *previous* block if the bid in the current block
|
||||
// indicates that the parent is full (and it hasn't already been applied).
|
||||
state_root = if block.fork_name_unchecked().gloas_enabled()
|
||||
&& self.state.slot() == self.state.latest_block_header().slot
|
||||
&& self.state.payload_status() == StatePayloadStatus::Pending
|
||||
{
|
||||
let latest_bid_block_hash = self
|
||||
.state
|
||||
.latest_execution_payload_bid()
|
||||
.map_err(BlockReplayError::from)?
|
||||
.block_hash;
|
||||
|
||||
// Similar to `is_parent_block_full`, but reading the block hash from the
|
||||
// not-yet-applied `block`. The slot 0 case covers genesis (no block replay reqd).
|
||||
if self.state.slot() != 0 && block.is_parent_block_full(latest_bid_block_hash) {
|
||||
let envelope = next_envelope_at_slot(self.state.slot())?;
|
||||
// State root for the next slot processing is now the envelope's state root.
|
||||
self.apply_payload_envelope(&envelope, state_root)?
|
||||
} else {
|
||||
// Empty payload at this slot, the state root is unchanged from when the
|
||||
// beacon block was applied.
|
||||
state_root
|
||||
}
|
||||
} else {
|
||||
// Pre-Gloas or at skipped slots post-Gloas, the state root of the parent state
|
||||
// is always the output from `self.get_state_root`.
|
||||
state_root
|
||||
};
|
||||
let state_root = self.get_state_root(&blocks, i)?;
|
||||
|
||||
if let Some(ref mut pre_slot_hook) = self.pre_slot_hook {
|
||||
pre_slot_hook(state_root, &mut self.state)?;
|
||||
@@ -380,24 +268,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the last payload if desired.
|
||||
let mut opt_state_root = if let StatePayloadStatus::Full = self.desired_state_payload_status
|
||||
&& let Some(last_block) = blocks.last()
|
||||
{
|
||||
let envelope = next_envelope_at_slot(self.state.slot())?;
|
||||
Some(self.apply_payload_envelope(&envelope, last_block.state_root())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(target_slot) = target_slot {
|
||||
while self.state.slot() < target_slot {
|
||||
// Read state root from `opt_state_root` if a payload was just applied.
|
||||
let state_root = if let Some(root) = opt_state_root.take() {
|
||||
root
|
||||
} else {
|
||||
self.get_state_root(&blocks, blocks.len())?
|
||||
};
|
||||
let state_root = self.get_state_root(&blocks, blocks.len())?;
|
||||
|
||||
if let Some(ref mut pre_slot_hook) = self.pre_slot_hook {
|
||||
pre_slot_hook(state_root, &mut self.state)?;
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
use crate::BlockProcessingError;
|
||||
use crate::VerifySignatures;
|
||||
use crate::per_block_processing::compute_timestamp_at_slot;
|
||||
use crate::per_block_processing::process_operations::{
|
||||
process_consolidation_requests, process_deposit_requests_post_gloas,
|
||||
process_withdrawal_requests,
|
||||
};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use safe_arith::ArithError;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
BeaconState, BeaconStateError, BuilderIndex, BuilderPendingPayment, ChainSpec, EthSpec,
|
||||
ExecutionBlockHash, Hash256, SignedExecutionPayloadEnvelope, Slot,
|
||||
BeaconState, BeaconStateError, BuilderIndex, ChainSpec, EthSpec, ExecutionBlockHash, Hash256,
|
||||
SignedExecutionPayloadEnvelope, Slot,
|
||||
};
|
||||
|
||||
macro_rules! envelope_verify {
|
||||
@@ -20,29 +15,11 @@ macro_rules! envelope_verify {
|
||||
};
|
||||
}
|
||||
|
||||
/// The strategy to be used when validating the payloads state root.
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum VerifyStateRoot {
|
||||
/// Validate state root.
|
||||
True,
|
||||
/// Do not validate state root. Use with caution.
|
||||
/// This should only be used when first constructing the payload envelope.
|
||||
False,
|
||||
}
|
||||
|
||||
impl VerifyStateRoot {
|
||||
pub fn is_true(self) -> bool {
|
||||
self == VerifyStateRoot::True
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EnvelopeProcessingError {
|
||||
/// Bad Signature
|
||||
BadSignature,
|
||||
BeaconStateError(BeaconStateError),
|
||||
BlockProcessingError(BlockProcessingError),
|
||||
ArithError(ArithError),
|
||||
/// Envelope doesn't match latest beacon block header
|
||||
LatestBlockHeaderMismatch {
|
||||
@@ -89,15 +66,11 @@ pub enum EnvelopeProcessingError {
|
||||
state: u64,
|
||||
envelope: u64,
|
||||
},
|
||||
// Invalid state root
|
||||
InvalidStateRoot {
|
||||
state: Hash256,
|
||||
// The execution requests root doesn't match the committed bid
|
||||
ExecutionRequestsRootMismatch {
|
||||
committed_bid: Hash256,
|
||||
envelope: Hash256,
|
||||
},
|
||||
// BitFieldError
|
||||
BitFieldError(ssz::BitfieldError),
|
||||
// Some kind of error calculating the builder payment index
|
||||
BuilderPaymentIndexOutOfBounds(usize),
|
||||
/// The envelope was deemed invalid by the execution engine.
|
||||
ExecutionInvalid,
|
||||
}
|
||||
@@ -108,50 +81,44 @@ impl From<BeaconStateError> for EnvelopeProcessingError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockProcessingError> for EnvelopeProcessingError {
|
||||
fn from(e: BlockProcessingError) -> Self {
|
||||
EnvelopeProcessingError::BlockProcessingError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArithError> for EnvelopeProcessingError {
|
||||
fn from(e: ArithError) -> Self {
|
||||
EnvelopeProcessingError::ArithError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes a `SignedExecutionPayloadEnvelope`
|
||||
/// Verifies a `SignedExecutionPayloadEnvelope` against the beacon state.
|
||||
///
|
||||
/// This function does all the state modifications inside `process_execution_payload()`
|
||||
pub fn process_execution_payload_envelope<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
parent_state_root: Option<Hash256>,
|
||||
/// This function performs pure verification with no state mutation. The execution requests
|
||||
/// from the envelope are deferred to be processed in the next block via
|
||||
/// `process_parent_execution_payload`.
|
||||
///
|
||||
/// `block_state_root` should be the post-block state root (used to fill in the block header
|
||||
/// for beacon_block_root verification). If `None`, the latest_block_header must already have
|
||||
/// its state_root filled in.
|
||||
pub fn verify_execution_payload_envelope<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
signed_envelope: &SignedExecutionPayloadEnvelope<E>,
|
||||
verify_signatures: VerifySignatures,
|
||||
verify_state_root: VerifyStateRoot,
|
||||
block_state_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EnvelopeProcessingError> {
|
||||
if verify_signatures.is_true() {
|
||||
// Verify Signed Envelope Signature
|
||||
if !signed_envelope.verify_signature_with_state(state, spec)? {
|
||||
return Err(EnvelopeProcessingError::BadSignature);
|
||||
}
|
||||
if verify_signatures.is_true() && !signed_envelope.verify_signature_with_state(state, spec)? {
|
||||
return Err(EnvelopeProcessingError::BadSignature);
|
||||
}
|
||||
|
||||
let envelope = &signed_envelope.message;
|
||||
let payload = &envelope.payload;
|
||||
let execution_requests = &envelope.execution_requests;
|
||||
|
||||
// Cache latest block header state root
|
||||
if state.latest_block_header().state_root == Hash256::default() {
|
||||
let previous_state_root = parent_state_root
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| state.canonical_root())?;
|
||||
state.latest_block_header_mut().state_root = previous_state_root;
|
||||
// Verify consistency with the beacon block.
|
||||
// Use a copy of the header with state_root filled in, matching the spec's approach.
|
||||
let mut header = state.latest_block_header().clone();
|
||||
if header.state_root == Hash256::default() {
|
||||
// The caller must provide the post-block state root so we can compute
|
||||
// the block header root without mutating state.
|
||||
header.state_root = block_state_root;
|
||||
}
|
||||
|
||||
// Verify consistency with the beacon block
|
||||
let latest_block_header_root = state.latest_block_header().tree_hash_root();
|
||||
let latest_block_header_root = header.tree_hash_root();
|
||||
envelope_verify!(
|
||||
envelope.beacon_block_root == latest_block_header_root,
|
||||
EnvelopeProcessingError::LatestBlockHeaderMismatch {
|
||||
@@ -160,9 +127,9 @@ pub fn process_execution_payload_envelope<E: EthSpec>(
|
||||
}
|
||||
);
|
||||
envelope_verify!(
|
||||
envelope.slot == state.slot(),
|
||||
envelope.slot() == state.slot(),
|
||||
EnvelopeProcessingError::SlotMismatch {
|
||||
envelope_slot: envelope.slot,
|
||||
envelope_slot: envelope.slot(),
|
||||
parent_state_slot: state.slot(),
|
||||
}
|
||||
);
|
||||
@@ -238,59 +205,17 @@ pub fn process_execution_payload_envelope<E: EthSpec>(
|
||||
}
|
||||
);
|
||||
|
||||
// Verify execution requests root matches committed bid
|
||||
let execution_requests_root = envelope.execution_requests.tree_hash_root();
|
||||
envelope_verify!(
|
||||
execution_requests_root == committed_bid.execution_requests_root,
|
||||
EnvelopeProcessingError::ExecutionRequestsRootMismatch {
|
||||
committed_bid: committed_bid.execution_requests_root,
|
||||
envelope: execution_requests_root,
|
||||
}
|
||||
);
|
||||
|
||||
// TODO(gloas): newPayload happens here in the spec, ensure we wire that up correctly
|
||||
|
||||
process_deposit_requests_post_gloas(state, &execution_requests.deposits, spec)?;
|
||||
process_withdrawal_requests(state, &execution_requests.withdrawals, spec)?;
|
||||
process_consolidation_requests(state, &execution_requests.consolidations, spec)?;
|
||||
|
||||
// Queue the builder payment
|
||||
let payment_index = E::slots_per_epoch()
|
||||
.safe_add(state.slot().as_u64().safe_rem(E::slots_per_epoch())?)?
|
||||
as usize;
|
||||
let payment_mut = state
|
||||
.builder_pending_payments_mut()?
|
||||
.get_mut(payment_index)
|
||||
.ok_or(EnvelopeProcessingError::BuilderPaymentIndexOutOfBounds(
|
||||
payment_index,
|
||||
))?;
|
||||
|
||||
// We have re-ordered the blanking out of the pending payment to avoid a double-lookup.
|
||||
// This is semantically equivalent to the ordering used by the spec because we have taken a
|
||||
// clone of the payment prior to doing the write.
|
||||
let payment_withdrawal = payment_mut.withdrawal.clone();
|
||||
*payment_mut = BuilderPendingPayment::default();
|
||||
|
||||
let amount = payment_withdrawal.amount;
|
||||
if amount > 0 {
|
||||
state
|
||||
.builder_pending_withdrawals_mut()?
|
||||
.push(payment_withdrawal)
|
||||
.map_err(|e| EnvelopeProcessingError::BeaconStateError(e.into()))?;
|
||||
}
|
||||
|
||||
// Cache the execution payload hash
|
||||
let availability_index = state
|
||||
.slot()
|
||||
.as_usize()
|
||||
.safe_rem(E::slots_per_historical_root())?;
|
||||
state
|
||||
.execution_payload_availability_mut()?
|
||||
.set(availability_index, true)
|
||||
.map_err(EnvelopeProcessingError::BitFieldError)?;
|
||||
*state.latest_block_hash_mut()? = payload.block_hash;
|
||||
|
||||
if verify_state_root.is_true() {
|
||||
// Verify the state root
|
||||
let state_root = state.canonical_root()?;
|
||||
envelope_verify!(
|
||||
envelope.state_root == state_root,
|
||||
EnvelopeProcessingError::InvalidStateRoot {
|
||||
state: state_root,
|
||||
envelope: envelope.state_root,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -167,9 +167,21 @@ pub fn initialize_beacon_state_from_eth1<E: EthSpec>(
|
||||
// Remove intermediate Fulu fork from `state.fork`.
|
||||
state.fork_mut().previous_version = spec.gloas_fork_version;
|
||||
|
||||
// Override latest execution payload header.
|
||||
// Here's where we *would* clone the header but there is no header here so..
|
||||
// TODO(EIP7732): check this
|
||||
// The genesis block's bid must have block_hash = 0x00 per spec (empty payload).
|
||||
// Retain the EL genesis hash in latest_block_hash and parent_block_hash so the
|
||||
// first post-genesis proposer can build on the correct EL head.
|
||||
let el_genesis_hash = state.latest_execution_payload_bid()?.block_hash;
|
||||
let bid = state.latest_execution_payload_bid_mut()?;
|
||||
bid.parent_block_hash = el_genesis_hash;
|
||||
bid.block_hash = ExecutionBlockHash::default();
|
||||
|
||||
// Update latest_block_header to reflect the Gloas genesis block body which contains
|
||||
// the EL genesis hash in the signed_execution_payload_bid. This is needed because
|
||||
// BeaconState::new() created the header from BeaconBlock::empty() which has zero bid
|
||||
// fields, but the spec requires the genesis block's bid to contain the EL block hash
|
||||
// and the tree hash root of empty ExecutionRequests.
|
||||
let block = genesis_block(&state, spec)?;
|
||||
state.latest_block_header_mut().body_root = block.body_root();
|
||||
}
|
||||
|
||||
// Now that we have our validators, initialize the caches (including the committees)
|
||||
@@ -181,6 +193,28 @@ pub fn initialize_beacon_state_from_eth1<E: EthSpec>(
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Create an unsigned genesis `BeaconBlock` whose body matches the genesis state.
|
||||
///
|
||||
/// For Gloas, the block's `signed_execution_payload_bid` is populated from the state's
|
||||
/// `latest_execution_payload_bid` so that the body root is consistent with
|
||||
/// `state.latest_block_header.body_root`.
|
||||
///
|
||||
/// The returned block has `state_root == Hash256::ZERO`; callers that need the real
|
||||
/// state root should set it themselves.
|
||||
pub fn genesis_block<E: EthSpec>(
|
||||
genesis_state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<BeaconBlock<E>, BeaconStateError> {
|
||||
let mut block = BeaconBlock::empty(spec);
|
||||
if let Ok(block) = block.as_gloas_mut() {
|
||||
let state_bid = genesis_state.latest_execution_payload_bid()?;
|
||||
let bid = &mut block.body.signed_execution_payload_bid.message;
|
||||
bid.block_hash = state_bid.block_hash;
|
||||
bid.execution_requests_root = state_bid.execution_requests_root;
|
||||
}
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
/// Determine whether a candidate genesis state is suitable for starting the chain.
|
||||
pub fn is_valid_genesis_state<E: EthSpec>(state: &BeaconState<E>, spec: &ChainSpec) -> bool {
|
||||
state
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
let block = signed_block.message();
|
||||
|
||||
// Verify that the `SignedBeaconBlock` instantiation matches the fork at `signed_block.slot()`.
|
||||
signed_block
|
||||
let fork_name = signed_block
|
||||
.fork_name(spec)
|
||||
.map_err(BlockProcessingError::InconsistentBlockFork)?;
|
||||
|
||||
@@ -129,6 +129,11 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
.fork_name(spec)
|
||||
.map_err(BlockProcessingError::InconsistentStateFork)?;
|
||||
|
||||
// Process deferred execution requests from the parent's envelope.
|
||||
if fork_name.gloas_enabled() {
|
||||
process_parent_execution_payload(state, block, spec)?;
|
||||
}
|
||||
|
||||
// Build epoch cache if it hasn't already been built, or if it is no longer valid
|
||||
initialize_epoch_cache(state, spec)?;
|
||||
initialize_progressive_balances_cache(state, spec)?;
|
||||
@@ -531,6 +536,139 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
|
||||
.and_then(|since_genesis| state.genesis_time().safe_add(since_genesis))
|
||||
}
|
||||
|
||||
/// Process the parent block's deferred execution payload effects.
|
||||
///
|
||||
/// This implements the spec's `process_parent_execution_payload` function, which validates
|
||||
/// the parent execution requests and delegates to `apply_parent_execution_payload` if the
|
||||
/// parent block was full. This is called at the beginning of block processing, before
|
||||
/// `process_block_header`.
|
||||
///
|
||||
/// `process_parent_execution_payload` must be called before `process_execution_payload_bid`
|
||||
/// (which overwrites `state.latest_execution_payload_bid`).
|
||||
pub fn process_parent_execution_payload<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
state: &mut BeaconState<E>,
|
||||
block: BeaconBlockRef<'_, E, Payload>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let bid_parent_block_hash = block
|
||||
.body()
|
||||
.signed_execution_payload_bid()?
|
||||
.message
|
||||
.parent_block_hash;
|
||||
let parent_bid = state.latest_execution_payload_bid()?.clone();
|
||||
let requests = block.body().parent_execution_requests()?;
|
||||
|
||||
let is_genesis_block = parent_bid.block_hash == ExecutionBlockHash::zero();
|
||||
let is_parent_block_empty = bid_parent_block_hash != parent_bid.block_hash;
|
||||
|
||||
if is_genesis_block || is_parent_block_empty {
|
||||
// Parent was EMPTY -- no execution requests expected
|
||||
block_verify!(
|
||||
*requests == ExecutionRequests::default(),
|
||||
BlockProcessingError::NonEmptyParentExecutionRequests
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Parent was FULL -- verify the bid commitment and apply the payload
|
||||
let requests_root = requests.tree_hash_root();
|
||||
block_verify!(
|
||||
requests_root == parent_bid.execution_requests_root,
|
||||
BlockProcessingError::ExecutionRequestsRootMismatch {
|
||||
expected: parent_bid.execution_requests_root,
|
||||
found: requests_root,
|
||||
}
|
||||
);
|
||||
|
||||
apply_parent_execution_payload(state, &parent_bid, requests, spec)
|
||||
}
|
||||
|
||||
/// Apply the parent execution payload's deferred effects to the state.
|
||||
///
|
||||
/// This implements the spec's `apply_parent_execution_payload` function:
|
||||
/// 1. Processes deposits, withdrawals, and consolidations from execution requests
|
||||
/// 2. Queues the builder pending payment from the parent's committed bid
|
||||
/// 3. Updates `execution_payload_availability` and `latest_block_hash`
|
||||
pub fn apply_parent_execution_payload<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
parent_bid: &ExecutionPayloadBid<E>,
|
||||
requests: &ExecutionRequests<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let parent_slot = parent_bid.slot;
|
||||
let parent_epoch = parent_slot.epoch(E::slots_per_epoch());
|
||||
|
||||
// Process execution requests from the parent's payload
|
||||
process_operations::process_deposit_requests_post_gloas(state, &requests.deposits, spec)?;
|
||||
process_operations::process_withdrawal_requests(state, &requests.withdrawals, spec)?;
|
||||
process_operations::process_consolidation_requests(state, &requests.consolidations, spec)?;
|
||||
|
||||
// Queue the builder payment
|
||||
if parent_epoch == state.current_epoch() {
|
||||
let payment_index = E::slots_per_epoch()
|
||||
.safe_add(parent_slot.as_u64().safe_rem(E::slots_per_epoch())?)?
|
||||
as usize;
|
||||
settle_builder_payment(state, payment_index)?;
|
||||
} else if parent_epoch == state.previous_epoch() {
|
||||
let payment_index = parent_slot.as_u64().safe_rem(E::slots_per_epoch())? as usize;
|
||||
settle_builder_payment(state, payment_index)?;
|
||||
} else if parent_bid.value > 0 {
|
||||
// Parent is older than previous epoch -- payment entry has already been
|
||||
// settled or evicted by process_builder_pending_payments at epoch boundaries.
|
||||
// Append the withdrawal directly from the bid.
|
||||
state
|
||||
.builder_pending_withdrawals_mut()?
|
||||
.push(BuilderPendingWithdrawal {
|
||||
fee_recipient: parent_bid.fee_recipient,
|
||||
amount: parent_bid.value,
|
||||
builder_index: parent_bid.builder_index,
|
||||
})
|
||||
.map_err(|e| BlockProcessingError::BeaconStateError(e.into()))?;
|
||||
}
|
||||
|
||||
// Update execution payload availability for the parent slot
|
||||
let availability_index = parent_slot
|
||||
.as_usize()
|
||||
.safe_rem(E::slots_per_historical_root())?;
|
||||
state
|
||||
.execution_payload_availability_mut()?
|
||||
.set(availability_index, true)
|
||||
.map_err(BlockProcessingError::BitfieldError)?;
|
||||
|
||||
// Update latest_block_hash to the parent bid's block_hash
|
||||
*state.latest_block_hash_mut()? = parent_bid.block_hash;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Spec: `settle_builder_payment`.
|
||||
///
|
||||
/// Moves a pending payment from `builder_pending_payments[payment_index]` into
|
||||
/// `builder_pending_withdrawals`, then clears the slot.
|
||||
pub fn settle_builder_payment<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
payment_index: usize,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let payment_mut = state
|
||||
.builder_pending_payments_mut()?
|
||||
.get_mut(payment_index)
|
||||
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(
|
||||
payment_index,
|
||||
))?;
|
||||
|
||||
let withdrawal = payment_mut.withdrawal.clone();
|
||||
*payment_mut = BuilderPendingPayment::default();
|
||||
|
||||
if withdrawal.amount > 0 {
|
||||
state
|
||||
.builder_pending_withdrawals_mut()?
|
||||
.push(withdrawal)
|
||||
.map_err(|e| BlockProcessingError::BeaconStateError(e.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
state: &mut BeaconState<E>,
|
||||
block: BeaconBlockRef<'_, E, Payload>,
|
||||
|
||||
@@ -108,6 +108,13 @@ pub enum BlockProcessingError {
|
||||
},
|
||||
/// Builder payment index out of bounds (Gloas)
|
||||
BuilderPaymentIndexOutOfBounds(usize),
|
||||
/// The parent execution requests root doesn't match the committed bid
|
||||
ExecutionRequestsRootMismatch {
|
||||
expected: Hash256,
|
||||
found: Hash256,
|
||||
},
|
||||
/// Parent was not full but non-empty execution requests were provided
|
||||
NonEmptyParentExecutionRequests,
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for BlockProcessingError {
|
||||
|
||||
@@ -1014,7 +1014,7 @@ async fn block_replayer_peeking_state_roots() {
|
||||
let block_replayer = BlockReplayer::new(parent_state, &harness.chain.spec)
|
||||
.state_root_iter(state_root_iter.into_iter())
|
||||
.no_signature_verification()
|
||||
.apply_blocks(vec![target_block], vec![], None)
|
||||
.apply_blocks(vec![target_block], None)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -9,8 +9,8 @@ use safe_arith::{SafeArith, SafeArithIter};
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
AbstractExecPayload, BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload,
|
||||
ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra,
|
||||
ExpectedWithdrawalsGloas, Validator, Withdrawal, Withdrawals,
|
||||
ExecutionBlockHash, ExpectedWithdrawals, ExpectedWithdrawalsCapella,
|
||||
ExpectedWithdrawalsElectra, ExpectedWithdrawalsGloas, Validator, Withdrawal, Withdrawals,
|
||||
};
|
||||
|
||||
/// Compute the next batch of withdrawals which should be included in a block.
|
||||
@@ -494,7 +494,11 @@ pub mod gloas {
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
if !state.is_parent_block_full() {
|
||||
// Return early if the parent block is empty.
|
||||
let is_genesis_block = *state.latest_block_hash()? == ExecutionBlockHash::default();
|
||||
let is_parent_block_empty =
|
||||
*state.latest_block_hash()? != state.latest_execution_payload_bid()?.block_hash;
|
||||
if is_genesis_block || is_parent_block_empty {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ use ssz_types::BitVector;
|
||||
use ssz_types::FixedVector;
|
||||
use std::collections::HashSet;
|
||||
use std::mem;
|
||||
use tree_hash::TreeHash;
|
||||
use typenum::Unsigned;
|
||||
use types::{
|
||||
BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec,
|
||||
DepositData, EthSpec, ExecutionPayloadBid, Fork, is_builder_withdrawal_credential,
|
||||
DepositData, EthSpec, ExecutionPayloadBid, ExecutionRequests, Fork,
|
||||
is_builder_withdrawal_credential,
|
||||
};
|
||||
|
||||
/// Transform a `Fulu` state into a `Gloas` state.
|
||||
@@ -78,6 +80,7 @@ pub fn upgrade_state_to_gloas<E: EthSpec>(
|
||||
// Execution Bid
|
||||
latest_execution_payload_bid: ExecutionPayloadBid {
|
||||
block_hash: pre.latest_execution_payload_header.block_hash,
|
||||
execution_requests_root: ExecutionRequests::<E>::default().tree_hash_root(),
|
||||
..Default::default()
|
||||
},
|
||||
// Capella
|
||||
|
||||
Reference in New Issue
Block a user