mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
Gloas envelope consensus and more operations tests (#8781)
- Implement new `process_execution_payload` (as `process_execution_payload_envelope`). - Implement new processing for deposit requests, including logic for adding new builders to the registry with index reuse. - Enable a bunch more operations EF tests (most of them except bid processing/payload attestations/etc which we don't have code for yet). Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
278
consensus/state_processing/src/envelope_processing.rs
Normal file
278
consensus/state_processing/src/envelope_processing.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
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 tree_hash::TreeHash;
|
||||
use types::{
|
||||
BeaconState, BeaconStateError, BuilderIndex, BuilderPendingPayment, ChainSpec, EthSpec,
|
||||
ExecutionBlockHash, Hash256, SignedExecutionPayloadEnvelope, Slot,
|
||||
};
|
||||
|
||||
macro_rules! envelope_verify {
|
||||
($condition: expr, $result: expr) => {
|
||||
if !$condition {
|
||||
return Err($result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EnvelopeProcessingError {
|
||||
/// Bad Signature
|
||||
BadSignature,
|
||||
BeaconStateError(BeaconStateError),
|
||||
BlockProcessingError(BlockProcessingError),
|
||||
ArithError(ArithError),
|
||||
/// Envelope doesn't match latest beacon block header
|
||||
LatestBlockHeaderMismatch {
|
||||
envelope_root: Hash256,
|
||||
block_header_root: Hash256,
|
||||
},
|
||||
/// Envelope doesn't match latest beacon block slot
|
||||
SlotMismatch {
|
||||
envelope_slot: Slot,
|
||||
parent_state_slot: Slot,
|
||||
},
|
||||
/// The payload withdrawals don't match the state's payload withdrawals.
|
||||
WithdrawalsRootMismatch {
|
||||
state: Hash256,
|
||||
payload: Hash256,
|
||||
},
|
||||
// The builder index doesn't match the committed bid.
|
||||
BuilderIndexMismatch {
|
||||
committed_bid: BuilderIndex,
|
||||
envelope: BuilderIndex,
|
||||
},
|
||||
// The gas limit doesn't match the committed bid
|
||||
GasLimitMismatch {
|
||||
committed_bid: u64,
|
||||
envelope: u64,
|
||||
},
|
||||
// The block hash doesn't match the committed bid
|
||||
BlockHashMismatch {
|
||||
committed_bid: ExecutionBlockHash,
|
||||
envelope: ExecutionBlockHash,
|
||||
},
|
||||
// The parent hash doesn't match the previous execution payload
|
||||
ParentHashMismatch {
|
||||
state: ExecutionBlockHash,
|
||||
envelope: ExecutionBlockHash,
|
||||
},
|
||||
// The previous randao didn't match the payload
|
||||
PrevRandaoMismatch {
|
||||
committed_bid: Hash256,
|
||||
envelope: Hash256,
|
||||
},
|
||||
// The timestamp didn't match the payload
|
||||
TimestampMismatch {
|
||||
state: u64,
|
||||
envelope: u64,
|
||||
},
|
||||
// Invalid state root
|
||||
InvalidStateRoot {
|
||||
state: 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,
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for EnvelopeProcessingError {
|
||||
fn from(e: BeaconStateError) -> Self {
|
||||
EnvelopeProcessingError::BeaconStateError(e)
|
||||
}
|
||||
}
|
||||
|
||||
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`
|
||||
///
|
||||
/// 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>,
|
||||
signed_envelope: &SignedExecutionPayloadEnvelope<E>,
|
||||
verify_signatures: VerifySignatures,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let latest_block_header_root = state.latest_block_header().tree_hash_root();
|
||||
envelope_verify!(
|
||||
envelope.beacon_block_root == latest_block_header_root,
|
||||
EnvelopeProcessingError::LatestBlockHeaderMismatch {
|
||||
envelope_root: envelope.beacon_block_root,
|
||||
block_header_root: latest_block_header_root,
|
||||
}
|
||||
);
|
||||
envelope_verify!(
|
||||
envelope.slot == state.slot(),
|
||||
EnvelopeProcessingError::SlotMismatch {
|
||||
envelope_slot: envelope.slot,
|
||||
parent_state_slot: state.slot(),
|
||||
}
|
||||
);
|
||||
|
||||
// Verify consistency with the committed bid
|
||||
let committed_bid = state.latest_execution_payload_bid()?;
|
||||
envelope_verify!(
|
||||
envelope.builder_index == committed_bid.builder_index,
|
||||
EnvelopeProcessingError::BuilderIndexMismatch {
|
||||
committed_bid: committed_bid.builder_index,
|
||||
envelope: envelope.builder_index,
|
||||
}
|
||||
);
|
||||
envelope_verify!(
|
||||
committed_bid.prev_randao == payload.prev_randao,
|
||||
EnvelopeProcessingError::PrevRandaoMismatch {
|
||||
committed_bid: committed_bid.prev_randao,
|
||||
envelope: payload.prev_randao,
|
||||
}
|
||||
);
|
||||
|
||||
// Verify consistency with expected withdrawals
|
||||
// NOTE: we don't bother hashing here except in case of error, because we can just compare for
|
||||
// equality directly. This equality check could be more straight-forward if the types were
|
||||
// changed to match (currently we are comparing VariableList to List). This could happen
|
||||
// coincidentally when we adopt ProgressiveList.
|
||||
envelope_verify!(
|
||||
payload.withdrawals.len() == state.payload_expected_withdrawals()?.len()
|
||||
&& payload
|
||||
.withdrawals
|
||||
.iter()
|
||||
.eq(state.payload_expected_withdrawals()?.iter()),
|
||||
EnvelopeProcessingError::WithdrawalsRootMismatch {
|
||||
state: state.payload_expected_withdrawals()?.tree_hash_root(),
|
||||
payload: payload.withdrawals.tree_hash_root(),
|
||||
}
|
||||
);
|
||||
|
||||
// Verify the gas limit
|
||||
envelope_verify!(
|
||||
committed_bid.gas_limit == payload.gas_limit,
|
||||
EnvelopeProcessingError::GasLimitMismatch {
|
||||
committed_bid: committed_bid.gas_limit,
|
||||
envelope: payload.gas_limit,
|
||||
}
|
||||
);
|
||||
|
||||
// Verify the block hash
|
||||
envelope_verify!(
|
||||
committed_bid.block_hash == payload.block_hash,
|
||||
EnvelopeProcessingError::BlockHashMismatch {
|
||||
committed_bid: committed_bid.block_hash,
|
||||
envelope: payload.block_hash,
|
||||
}
|
||||
);
|
||||
|
||||
// Verify consistency of the parent hash with respect to the previous execution payload
|
||||
envelope_verify!(
|
||||
payload.parent_hash == *state.latest_block_hash()?,
|
||||
EnvelopeProcessingError::ParentHashMismatch {
|
||||
state: *state.latest_block_hash()?,
|
||||
envelope: payload.parent_hash,
|
||||
}
|
||||
);
|
||||
|
||||
// Verify timestamp
|
||||
let state_timestamp = compute_timestamp_at_slot(state, state.slot(), spec)?;
|
||||
envelope_verify!(
|
||||
payload.timestamp == state_timestamp,
|
||||
EnvelopeProcessingError::TimestampMismatch {
|
||||
state: state_timestamp,
|
||||
envelope: payload.timestamp,
|
||||
}
|
||||
);
|
||||
|
||||
// TODO(gloas): newPayload happens here in the spec, ensure we wire that up correctly
|
||||
|
||||
process_deposit_requests_post_gloas(state, &execution_requests.deposits, spec)?;
|
||||
|
||||
// TODO(gloas): gotta update these
|
||||
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;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
@@ -20,6 +20,7 @@ pub mod all_caches;
|
||||
pub mod block_replayer;
|
||||
pub mod common;
|
||||
pub mod consensus_context;
|
||||
pub mod envelope_processing;
|
||||
pub mod epoch_cache;
|
||||
pub mod genesis;
|
||||
pub mod per_block_processing;
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::common::{
|
||||
slash_validator,
|
||||
};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
|
||||
use bls::{PublicKeyBytes, SignatureBytes};
|
||||
use ssz_types::FixedVector;
|
||||
use typenum::U33;
|
||||
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
|
||||
@@ -38,9 +39,14 @@ pub fn process_operations<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?;
|
||||
}
|
||||
|
||||
if state.fork_name_unchecked().electra_enabled() {
|
||||
if state.fork_name_unchecked().electra_enabled() && !state.fork_name_unchecked().gloas_enabled()
|
||||
{
|
||||
state.update_pubkey_cache()?;
|
||||
process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?;
|
||||
process_deposit_requests_pre_gloas(
|
||||
state,
|
||||
&block_body.execution_requests()?.deposits,
|
||||
spec,
|
||||
)?;
|
||||
process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?;
|
||||
process_consolidation_requests(
|
||||
state,
|
||||
@@ -377,6 +383,31 @@ pub fn process_proposer_slashings<E: EthSpec>(
|
||||
verify_proposer_slashing(proposer_slashing, state, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
// [New in Gloas:EIP7732]
|
||||
// Remove the BuilderPendingPayment corresponding to this proposal
|
||||
// if it is still in the 2-epoch window.
|
||||
if state.fork_name_unchecked().gloas_enabled() {
|
||||
let slot = proposer_slashing.signed_header_1.message.slot;
|
||||
let proposal_epoch = slot.epoch(E::slots_per_epoch());
|
||||
let slot_in_epoch = slot.as_usize().safe_rem(E::SlotsPerEpoch::to_usize())?;
|
||||
|
||||
let payment_index = if proposal_epoch == state.current_epoch() {
|
||||
Some(E::SlotsPerEpoch::to_usize().safe_add(slot_in_epoch)?)
|
||||
} else if proposal_epoch == state.previous_epoch() {
|
||||
Some(slot_in_epoch)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(index) = payment_index {
|
||||
let payment = state
|
||||
.builder_pending_payments_mut()?
|
||||
.get_mut(index)
|
||||
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(index))?;
|
||||
*payment = BuilderPendingPayment::default();
|
||||
}
|
||||
}
|
||||
|
||||
slash_validator(
|
||||
state,
|
||||
proposer_slashing.signed_header_1.message.proposer_index as usize,
|
||||
@@ -736,7 +767,7 @@ pub fn process_withdrawal_requests<E: EthSpec>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_deposit_requests<E: EthSpec>(
|
||||
pub fn process_deposit_requests_pre_gloas<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
deposit_requests: &[DepositRequest],
|
||||
spec: &ChainSpec,
|
||||
@@ -763,6 +794,112 @@ pub fn process_deposit_requests<E: EthSpec>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_deposit_requests_post_gloas<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
deposit_requests: &[DepositRequest],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
for request in deposit_requests {
|
||||
process_deposit_request_post_gloas(state, request, spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_deposit_request_post_gloas<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
deposit_request: &DepositRequest,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// [New in Gloas:EIP7732]
|
||||
// Regardless of the withdrawal credentials prefix, if a builder/validator
|
||||
// already exists with this pubkey, apply the deposit to their balance
|
||||
// TODO(gloas): this could be more efficient in the builder case, see:
|
||||
// https://github.com/sigp/lighthouse/issues/8783
|
||||
let builder_index = state
|
||||
.builders()?
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, builder)| builder.pubkey == deposit_request.pubkey)
|
||||
.map(|(i, _)| i as u64);
|
||||
let is_builder = builder_index.is_some();
|
||||
|
||||
let validator_index = state.get_validator_index(&deposit_request.pubkey)?;
|
||||
let is_validator = validator_index.is_some();
|
||||
|
||||
let is_builder_prefix =
|
||||
is_builder_withdrawal_credential(deposit_request.withdrawal_credentials, spec);
|
||||
|
||||
if is_builder || (is_builder_prefix && !is_validator) {
|
||||
// Apply builder deposits immediately
|
||||
apply_deposit_for_builder(
|
||||
state,
|
||||
builder_index,
|
||||
deposit_request.pubkey,
|
||||
deposit_request.withdrawal_credentials,
|
||||
deposit_request.amount,
|
||||
deposit_request.signature.clone(),
|
||||
state.slot(),
|
||||
spec,
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Add validator deposits to the queue
|
||||
let slot = state.slot();
|
||||
state.pending_deposits_mut()?.push(PendingDeposit {
|
||||
pubkey: deposit_request.pubkey,
|
||||
withdrawal_credentials: deposit_request.withdrawal_credentials,
|
||||
amount: deposit_request.amount,
|
||||
signature: deposit_request.signature.clone(),
|
||||
slot,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn apply_deposit_for_builder<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
builder_index_opt: Option<BuilderIndex>,
|
||||
pubkey: PublicKeyBytes,
|
||||
withdrawal_credentials: Hash256,
|
||||
amount: u64,
|
||||
signature: SignatureBytes,
|
||||
slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
match builder_index_opt {
|
||||
None => {
|
||||
// Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
let deposit_data = DepositData {
|
||||
pubkey,
|
||||
withdrawal_credentials,
|
||||
amount,
|
||||
signature,
|
||||
};
|
||||
if is_valid_deposit_signature(&deposit_data, spec).is_ok() {
|
||||
state.add_builder_to_registry(
|
||||
pubkey,
|
||||
withdrawal_credentials,
|
||||
amount,
|
||||
slot,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Some(builder_index) => {
|
||||
state
|
||||
.builders_mut()?
|
||||
.get_mut(builder_index as usize)
|
||||
.ok_or(BeaconStateError::UnknownBuilder(builder_index))?
|
||||
.balance
|
||||
.safe_add_assign(amount)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Make sure to build the pubkey cache before calling this function
|
||||
pub fn process_consolidation_requests<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
|
||||
@@ -52,8 +52,6 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>(
|
||||
///
|
||||
/// Returns a descriptive `Err` if the attestation is malformed or does not accurately reflect the
|
||||
/// prior blocks in `state`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn verify_attestation_for_state<'ctxt, E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
attestation: AttestationRef<'ctxt, E>,
|
||||
@@ -94,8 +92,6 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>(
|
||||
}
|
||||
|
||||
/// Check target epoch and source checkpoint.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
fn verify_casper_ffg_vote<E: EthSpec>(
|
||||
attestation: AttestationRef<E>,
|
||||
state: &BeaconState<E>,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{
|
||||
ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash, ExecutionPayloadEnvelope, Fork,
|
||||
ForkName, Hash256, SignedRoot, Slot,
|
||||
BeaconState, BeaconStateError, ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash,
|
||||
ExecutionPayloadEnvelope, Fork, ForkName, Hash256, SignedRoot, Slot,
|
||||
consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
||||
};
|
||||
use bls::{PublicKey, Signature};
|
||||
use context_deserialize::context_deserialize;
|
||||
@@ -58,6 +59,42 @@ impl<E: EthSpec> SignedExecutionPayloadEnvelope<E> {
|
||||
|
||||
self.signature.verify(pubkey, message)
|
||||
}
|
||||
|
||||
/// Verify `self.signature` using keys drawn from the beacon state.
|
||||
pub fn verify_signature_with_state(
|
||||
&self,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
let builder_index = self.message.builder_index;
|
||||
|
||||
let pubkey_bytes = if builder_index == BUILDER_INDEX_SELF_BUILD {
|
||||
let validator_index = state.latest_block_header().proposer_index;
|
||||
state.get_validator(validator_index as usize)?.pubkey
|
||||
} else {
|
||||
state.get_builder(builder_index)?.pubkey
|
||||
};
|
||||
|
||||
// TODO(gloas): Could use pubkey cache on state here, but it probably isn't worth
|
||||
// it because this function is rarely used. Almost always the envelope should be signature
|
||||
// verified prior to consensus code running.
|
||||
let pubkey = pubkey_bytes.decompress()?;
|
||||
|
||||
// Ensure the state's epoch matches the message's epoch before determining the Fork.
|
||||
if self.epoch() != state.current_epoch() {
|
||||
return Err(BeaconStateError::SignedEnvelopeIncorrectEpoch {
|
||||
state_epoch: state.current_epoch(),
|
||||
envelope_epoch: self.epoch(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self.verify_signature(
|
||||
&pubkey,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
spec,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -23,7 +23,7 @@ use tree_hash_derive::TreeHash;
|
||||
use typenum::Unsigned;
|
||||
|
||||
use crate::{
|
||||
ExecutionBlockHash, ExecutionPayloadBid, Withdrawal,
|
||||
Address, ExecutionBlockHash, ExecutionPayloadBid, Withdrawal,
|
||||
attestation::{
|
||||
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
|
||||
ParticipationFlags, PendingAttestation,
|
||||
@@ -174,6 +174,8 @@ pub enum BeaconStateError {
|
||||
MerkleTreeError(merkle_proof::MerkleTreeError),
|
||||
PartialWithdrawalCountInvalid(usize),
|
||||
NonExecutionAddressWithdrawalCredential,
|
||||
WithdrawalCredentialMissingVersion,
|
||||
WithdrawalCredentialMissingAddress,
|
||||
NoCommitteeFound(CommitteeIndex),
|
||||
InvalidCommitteeIndex(CommitteeIndex),
|
||||
/// `Attestation.data.index` field is invalid in overloaded data index scenario.
|
||||
@@ -199,6 +201,10 @@ pub enum BeaconStateError {
|
||||
ProposerLookaheadOutOfBounds {
|
||||
i: usize,
|
||||
},
|
||||
SignedEnvelopeIncorrectEpoch {
|
||||
state_epoch: Epoch,
|
||||
envelope_epoch: Epoch,
|
||||
},
|
||||
InvalidIndicesCount,
|
||||
InvalidExecutionPayloadAvailabilityIndex(usize),
|
||||
}
|
||||
@@ -1920,6 +1926,15 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
.ok_or(BeaconStateError::UnknownValidator(validator_index))
|
||||
}
|
||||
|
||||
/// Safe indexer for the `builders` list.
|
||||
///
|
||||
/// Will return an error pre-Gloas, or for out-of-bounds indices.
|
||||
pub fn get_builder(&self, builder_index: BuilderIndex) -> Result<&Builder, BeaconStateError> {
|
||||
self.builders()?
|
||||
.get(builder_index as usize)
|
||||
.ok_or(BeaconStateError::UnknownBuilder(builder_index))
|
||||
}
|
||||
|
||||
/// Add a validator to the registry and return the validator index that was allocated for it.
|
||||
pub fn add_validator_to_registry(
|
||||
&mut self,
|
||||
@@ -1966,6 +1981,64 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
/// Add a builder to the registry and return the builder index that was allocated for it.
|
||||
pub fn add_builder_to_registry(
|
||||
&mut self,
|
||||
pubkey: PublicKeyBytes,
|
||||
withdrawal_credentials: Hash256,
|
||||
amount: u64,
|
||||
slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<BuilderIndex, BeaconStateError> {
|
||||
// We are not yet using the spec's `set_or_append_list`, but could consider it if it crops
|
||||
// up elsewhere. It has been retconned into the spec to support index reuse but so far
|
||||
// index reuse is only relevant for builders.
|
||||
let builder_index = self.get_index_for_new_builder()?;
|
||||
let builders = self.builders_mut()?;
|
||||
|
||||
let version = *withdrawal_credentials
|
||||
.as_slice()
|
||||
.first()
|
||||
.ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?;
|
||||
let execution_address = withdrawal_credentials
|
||||
.as_slice()
|
||||
.get(12..)
|
||||
.and_then(|bytes| Address::try_from(bytes).ok())
|
||||
.ok_or(BeaconStateError::WithdrawalCredentialMissingAddress)?;
|
||||
|
||||
let builder = Builder {
|
||||
pubkey,
|
||||
version,
|
||||
execution_address,
|
||||
balance: amount,
|
||||
deposit_epoch: slot.epoch(E::slots_per_epoch()),
|
||||
withdrawable_epoch: spec.far_future_epoch,
|
||||
};
|
||||
|
||||
if builder_index == builders.len() as u64 {
|
||||
builders.push(builder)?;
|
||||
} else {
|
||||
*builders
|
||||
.get_mut(builder_index as usize)
|
||||
.ok_or(BeaconStateError::UnknownBuilder(builder_index))? = builder;
|
||||
}
|
||||
Ok(builder_index)
|
||||
}
|
||||
|
||||
// TODO(gloas): Optimize this function if we see a lot of registered builders on-chain.
|
||||
// A cache here could be quite fiddly because this calculation depends on withdrawable epoch
|
||||
// and balance - a cache for this would need to be updated whenever either of those fields
|
||||
// changes.
|
||||
pub fn get_index_for_new_builder(&self) -> Result<BuilderIndex, BeaconStateError> {
|
||||
let current_epoch = self.current_epoch();
|
||||
for (index, builder) in self.builders()?.iter().enumerate() {
|
||||
if builder.withdrawable_epoch <= current_epoch && builder.balance == 0 {
|
||||
return Ok(index as u64);
|
||||
}
|
||||
}
|
||||
Ok(self.builders()?.len() as u64)
|
||||
}
|
||||
|
||||
/// Safe copy-on-write accessor for the `validators` list.
|
||||
pub fn get_validator_cow(
|
||||
&mut self,
|
||||
|
||||
@@ -4,6 +4,8 @@ mod validator_registration_data;
|
||||
mod validator_subscription;
|
||||
|
||||
pub use proposer_preparation_data::ProposerPreparationData;
|
||||
pub use validator::{Validator, is_compounding_withdrawal_credential};
|
||||
pub use validator::{
|
||||
Validator, is_builder_withdrawal_credential, is_compounding_withdrawal_credential,
|
||||
};
|
||||
pub use validator_registration_data::{SignedValidatorRegistrationData, ValidatorRegistrationData};
|
||||
pub use validator_subscription::ValidatorSubscription;
|
||||
|
||||
@@ -319,6 +319,14 @@ pub fn is_compounding_withdrawal_credential(
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_builder_withdrawal_credential(withdrawal_credentials: Hash256, spec: &ChainSpec) -> bool {
|
||||
withdrawal_credentials
|
||||
.as_slice()
|
||||
.first()
|
||||
.map(|prefix_byte| *prefix_byte == spec.builder_withdrawal_prefix_byte)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -48,19 +48,9 @@ excluded_paths = [
|
||||
"tests/.*/eip7732",
|
||||
"tests/.*/eip7805",
|
||||
# TODO(gloas): remove these ignores as more Gloas operations are implemented
|
||||
"tests/.*/gloas/operations/attester_slashing/.*",
|
||||
"tests/.*/gloas/operations/block_header/.*",
|
||||
"tests/.*/gloas/operations/bls_to_execution_change/.*",
|
||||
"tests/.*/gloas/operations/consolidation_request/.*",
|
||||
"tests/.*/gloas/operations/deposit/.*",
|
||||
"tests/.*/gloas/operations/deposit_request/.*",
|
||||
"tests/.*/gloas/operations/execution_payload/.*",
|
||||
"tests/.*/gloas/operations/execution_payload_bid/.*",
|
||||
"tests/.*/gloas/operations/payload_attestation/.*",
|
||||
"tests/.*/gloas/operations/proposer_slashing/.*",
|
||||
"tests/.*/gloas/operations/sync_aggregate/.*",
|
||||
"tests/.*/gloas/operations/voluntary_exit/.*",
|
||||
"tests/.*/gloas/operations/withdrawal_request/.*",
|
||||
# TODO(EIP-7732): remove these ignores as Gloas consensus is implemented
|
||||
"tests/.*/gloas/epoch_processing/.*",
|
||||
"tests/.*/gloas/finality/.*",
|
||||
|
||||
@@ -7,10 +7,12 @@ use ssz::Decode;
|
||||
use state_processing::common::update_progressive_balances_cache::initialize_progressive_balances_cache;
|
||||
use state_processing::epoch_cache::initialize_epoch_cache;
|
||||
use state_processing::per_block_processing::process_operations::{
|
||||
process_consolidation_requests, process_deposit_requests, process_withdrawal_requests,
|
||||
process_consolidation_requests, process_deposit_requests_post_gloas,
|
||||
process_deposit_requests_pre_gloas, process_withdrawal_requests,
|
||||
};
|
||||
use state_processing::{
|
||||
ConsensusContext,
|
||||
envelope_processing::{EnvelopeProcessingError, process_execution_payload_envelope},
|
||||
per_block_processing::{
|
||||
VerifyBlockRoot, VerifySignatures,
|
||||
errors::BlockProcessingError,
|
||||
@@ -29,7 +31,7 @@ use types::{
|
||||
BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu,
|
||||
BeaconState, BlindedPayload, ConsolidationRequest, Deposit, DepositRequest, ExecutionPayload,
|
||||
ForkVersionDecode, FullPayload, ProposerSlashing, SignedBlsToExecutionChange,
|
||||
SignedVoluntaryExit, SyncAggregate, WithdrawalRequest,
|
||||
SignedExecutionPayloadEnvelope, SignedVoluntaryExit, SyncAggregate, WithdrawalRequest,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
@@ -59,6 +61,8 @@ pub struct Operations<E: EthSpec, O: Operation<E>> {
|
||||
}
|
||||
|
||||
pub trait Operation<E: EthSpec>: Debug + Sync + Sized {
|
||||
type Error: Debug;
|
||||
|
||||
fn handler_name() -> String;
|
||||
|
||||
fn filename() -> String {
|
||||
@@ -76,10 +80,12 @@ pub trait Operation<E: EthSpec>: Debug + Sync + Sized {
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
_: &Operations<E, Self>,
|
||||
) -> Result<(), BlockProcessingError>;
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for Attestation<E> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"attestation".into()
|
||||
}
|
||||
@@ -132,6 +138,8 @@ impl<E: EthSpec> Operation<E> for Attestation<E> {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"attester_slashing".into()
|
||||
}
|
||||
@@ -163,6 +171,8 @@ impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for Deposit {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"deposit".into()
|
||||
}
|
||||
@@ -187,6 +197,8 @@ impl<E: EthSpec> Operation<E> for Deposit {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for ProposerSlashing {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"proposer_slashing".into()
|
||||
}
|
||||
@@ -214,6 +226,8 @@ impl<E: EthSpec> Operation<E> for ProposerSlashing {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for SignedVoluntaryExit {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"voluntary_exit".into()
|
||||
}
|
||||
@@ -238,6 +252,8 @@ impl<E: EthSpec> Operation<E> for SignedVoluntaryExit {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for BeaconBlock<E> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"block_header".into()
|
||||
}
|
||||
@@ -269,6 +285,8 @@ impl<E: EthSpec> Operation<E> for BeaconBlock<E> {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for SyncAggregate<E> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"sync_aggregate".into()
|
||||
}
|
||||
@@ -297,6 +315,8 @@ impl<E: EthSpec> Operation<E> for SyncAggregate<E> {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, FullPayload<E>> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"execution_payload".into()
|
||||
}
|
||||
@@ -306,7 +326,7 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, FullPayload<E>> {
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
|
||||
fork_name.bellatrix_enabled()
|
||||
fork_name.bellatrix_enabled() && !fork_name.gloas_enabled()
|
||||
}
|
||||
|
||||
fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
|
||||
@@ -317,8 +337,7 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, FullPayload<E>> {
|
||||
ForkName::Deneb => BeaconBlockBody::Deneb(<_>::from_ssz_bytes(bytes)?),
|
||||
ForkName::Electra => BeaconBlockBody::Electra(<_>::from_ssz_bytes(bytes)?),
|
||||
ForkName::Fulu => BeaconBlockBody::Fulu(<_>::from_ssz_bytes(bytes)?),
|
||||
// TODO(EIP-7732): See if we need to handle Gloas here
|
||||
_ => panic!(),
|
||||
_ => panic!("Not supported after Gloas"),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -340,7 +359,10 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, FullPayload<E>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, BlindedPayload<E>> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"execution_payload".into()
|
||||
}
|
||||
@@ -350,7 +372,7 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, BlindedPayload<E>> {
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
|
||||
fork_name.bellatrix_enabled()
|
||||
fork_name.bellatrix_enabled() && !fork_name.gloas_enabled()
|
||||
}
|
||||
|
||||
fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
|
||||
@@ -377,8 +399,7 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, BlindedPayload<E>> {
|
||||
let inner = <BeaconBlockBodyFulu<E, FullPayload<E>>>::from_ssz_bytes(bytes)?;
|
||||
BeaconBlockBody::Fulu(inner.clone_as_blinded())
|
||||
}
|
||||
// TODO(EIP-7732): See if we need to handle Gloas here
|
||||
_ => panic!(),
|
||||
_ => panic!("Not supported after Gloas"),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -401,7 +422,46 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, BlindedPayload<E>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for SignedExecutionPayloadEnvelope<E> {
|
||||
type Error = EnvelopeProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"execution_payload".into()
|
||||
}
|
||||
|
||||
fn filename() -> String {
|
||||
"signed_envelope.ssz_snappy".into()
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
|
||||
fork_name.gloas_enabled()
|
||||
}
|
||||
|
||||
fn decode(path: &Path, _: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
|
||||
ssz_decode_file(path)
|
||||
}
|
||||
|
||||
fn apply_to(
|
||||
&self,
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
extra: &Operations<E, Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let valid = extra
|
||||
.execution_metadata
|
||||
.as_ref()
|
||||
.is_some_and(|e| e.execution_valid);
|
||||
if valid {
|
||||
process_execution_payload_envelope(state, None, self, VerifySignatures::True, spec)
|
||||
} else {
|
||||
Err(EnvelopeProcessingError::ExecutionInvalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"withdrawals".into()
|
||||
}
|
||||
@@ -448,6 +508,8 @@ impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for SignedBlsToExecutionChange {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"bls_to_execution_change".into()
|
||||
}
|
||||
@@ -480,6 +542,8 @@ impl<E: EthSpec> Operation<E> for SignedBlsToExecutionChange {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for WithdrawalRequest {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"withdrawal_request".into()
|
||||
}
|
||||
@@ -504,6 +568,8 @@ impl<E: EthSpec> Operation<E> for WithdrawalRequest {
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for DepositRequest {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"deposit_request".into()
|
||||
}
|
||||
@@ -522,11 +588,17 @@ impl<E: EthSpec> Operation<E> for DepositRequest {
|
||||
spec: &ChainSpec,
|
||||
_extra: &Operations<E, Self>,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
process_deposit_requests(state, std::slice::from_ref(self), spec)
|
||||
if state.fork_name_unchecked().gloas_enabled() {
|
||||
process_deposit_requests_post_gloas(state, std::slice::from_ref(self), spec)
|
||||
} else {
|
||||
process_deposit_requests_pre_gloas(state, std::slice::from_ref(self), spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for ConsolidationRequest {
|
||||
type Error = BlockProcessingError;
|
||||
|
||||
fn handler_name() -> String {
|
||||
"consolidation_request".into()
|
||||
}
|
||||
|
||||
@@ -1135,11 +1135,20 @@ impl<E: EthSpec + TypeName, O: Operation<E>> Handler for OperationsHandler<E, O>
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
|
||||
// TODO(gloas): So far only withdrawals tests are enabled for Gloas.
|
||||
Self::Case::is_enabled_for_fork(fork_name)
|
||||
&& (!fork_name.gloas_enabled()
|
||||
|| self.handler_name() == "attestation"
|
||||
|| self.handler_name() == "attester_slashing"
|
||||
|| self.handler_name() == "bls_to_execution_change"
|
||||
|| self.handler_name() == "consolidation_request"
|
||||
|| self.handler_name() == "deposit_request"
|
||||
|| self.handler_name() == "deposit"
|
||||
|| self.handler_name() == "execution_payload"
|
||||
|| self.handler_name() == "proposer_slashing"
|
||||
|| self.handler_name() == "sync_aggregate"
|
||||
|| self.handler_name() == "withdrawal_request"
|
||||
|| self.handler_name() == "withdrawals"
|
||||
|| self.handler_name() == "attestation")
|
||||
|| self.handler_name() == "voluntary_exit")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,12 @@ fn operations_execution_payload_blinded() {
|
||||
OperationsHandler::<MainnetEthSpec, BeaconBlockBody<_, BlindedPayload<_>>>::default().run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operations_execution_payload_envelope() {
|
||||
OperationsHandler::<MinimalEthSpec, SignedExecutionPayloadEnvelope<_>>::default().run();
|
||||
OperationsHandler::<MainnetEthSpec, SignedExecutionPayloadEnvelope<_>>::default().run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operations_withdrawals() {
|
||||
OperationsHandler::<MinimalEthSpec, WithdrawalsPayload<_>>::default().run();
|
||||
@@ -94,7 +100,7 @@ fn operations_withdrawals() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operations_withdrawal_reqeusts() {
|
||||
fn operations_withdrawal_requests() {
|
||||
OperationsHandler::<MinimalEthSpec, WithdrawalRequest>::default().run();
|
||||
OperationsHandler::<MainnetEthSpec, WithdrawalRequest>::default().run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user