Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 167 additions & 3 deletions consensus/state_processing/src/per_block_processing.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::consensus_context::ConsensusContext;
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
use errors::{
BlockOperationError, BlockProcessingError, ExecutionPayloadBidInvalid, HeaderInvalid,
};
use rayon::prelude::*;
use safe_arith::{ArithError, SafeArith};
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
use signature_sets::{
block_proposal_signature_set, execution_payload_bid_signature_set,
get_builder_pubkey_from_state, get_pubkey_from_state, randao_signature_set,
};
use std::borrow::Cow;
use tree_hash::TreeHash;
use typenum::Unsigned;
use types::*;
use types::{consts::gloas::BUILDER_INDEX_SELF_BUILD, *};

pub use self::verify_attester_slashing::{
get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing,
Expand Down Expand Up @@ -522,3 +527,162 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
.safe_mul(spec.get_slot_duration().as_secs())
.and_then(|since_genesis| state.genesis_time().safe_add(since_genesis))
}

pub fn can_builder_cover_bid<E: EthSpec>(
state: &BeaconState<E>,
builder_index: BuilderIndex,
builder: &Builder,
bid_amount: u64,
spec: &ChainSpec,
) -> Result<bool, BlockProcessingError> {
let builder_balance = builder.balance;
let pending_withdrawals_amount =
state.get_pending_balance_to_withdraw_for_builder(builder_index)?;
let min_balance = spec
.min_deposit_amount
.safe_add(pending_withdrawals_amount)?;
if builder_balance < min_balance {
Ok(false)
} else {
Ok(builder_balance.safe_sub(min_balance)? >= bid_amount)
}
}

pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>>(
state: &mut BeaconState<E>,
block: BeaconBlockRef<'_, E, Payload>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Verify the bid signature
let signed_bid = block.body().signed_execution_payload_bid()?;

let bid = &signed_bid.message;
let amount = bid.value;
let builder_index = bid.builder_index;

// For self-builds, amount must be zero regardless of withdrawal credential prefix
if builder_index == BUILDER_INDEX_SELF_BUILD {
block_verify!(
amount == 0,
ExecutionPayloadBidInvalid::SelfBuildNonZeroAmount.into()
);
block_verify!(
signed_bid.signature.is_infinity(),
ExecutionPayloadBidInvalid::BadSignature.into()
);
} else {
let builder = state.get_builder(builder_index)?;

// Verify that the builder is active
block_verify!(
builder.is_active_at_finalized_epoch(state.finalized_checkpoint().epoch, spec),
ExecutionPayloadBidInvalid::BuilderNotActive(builder_index).into()
);

// Verify that the builder has funds to cover the bid
block_verify!(
can_builder_cover_bid(state, builder_index, builder, amount, spec)?,
ExecutionPayloadBidInvalid::InsufficientBalance {
builder_index,
builder_balance: builder.balance,
bid_value: amount,
}
.into()
);

if verify_signatures.is_true() {
block_verify!(
// We know this is NOT a self-build, so there MUST be a signature set (func does not
// return None).
execution_payload_bid_signature_set(
state,
|i| get_builder_pubkey_from_state(state, i),
signed_bid,
spec
)?
.ok_or(ExecutionPayloadBidInvalid::BadSignature)?
.verify(),
ExecutionPayloadBidInvalid::BadSignature.into()
);
}
}

// Verify commitments are under limit
let max_blobs_per_block = spec.max_blobs_per_block(state.current_epoch()) as usize;
block_verify!(
bid.blob_kzg_commitments.len() <= max_blobs_per_block,
ExecutionPayloadBidInvalid::ExcessBlobCommitments {
max: max_blobs_per_block,
bid: bid.blob_kzg_commitments.len(),
}
.into()
);

// Verify that the bid is for the current slot
block_verify!(
bid.slot == block.slot(),
ExecutionPayloadBidInvalid::SlotMismatch {
bid_slot: bid.slot,
block_slot: block.slot(),
}
.into()
);

// Verify that the bid is for the right parent block
let latest_block_hash = state.latest_block_hash()?;
block_verify!(
bid.parent_block_hash == *latest_block_hash,
ExecutionPayloadBidInvalid::ParentBlockHashMismatch {
state_block_hash: *latest_block_hash,
bid_parent_hash: bid.parent_block_hash,
}
.into()
);

block_verify!(
bid.parent_block_root == block.parent_root(),
ExecutionPayloadBidInvalid::ParentBlockRootMismatch {
block_parent_root: block.parent_root(),
bid_parent_root: bid.parent_block_root,
}
.into()
);

let expected_randao = *state.get_randao_mix(state.current_epoch())?;
block_verify!(
bid.prev_randao == expected_randao,
ExecutionPayloadBidInvalid::PrevRandaoMismatch {
expected: expected_randao,
bid: bid.prev_randao,
}
.into()
);

// Record the pending payment if there is some payment
if amount > 0 {
let pending_payment = BuilderPendingPayment {
weight: 0,
withdrawal: BuilderPendingWithdrawal {
fee_recipient: bid.fee_recipient,
amount,
builder_index,
},
};

let payment_index = E::SlotsPerEpoch::to_usize()
.safe_add(bid.slot.as_usize().safe_rem(E::SlotsPerEpoch::to_usize())?)?;

*state
.builder_pending_payments_mut()?
.get_mut(payment_index)
.ok_or(BlockProcessingError::BeaconStateError(
BeaconStateError::InvalidBuilderPendingPaymentsIndex(payment_index),
))? = pending_payment;
}

// Cache the execution bid
*state.latest_execution_payload_bid_mut()? = bid.clone();

Ok(())
}
41 changes: 41 additions & 0 deletions consensus/state_processing/src/per_block_processing/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ pub enum BlockProcessingError {
IncorrectExpectedWithdrawalsVariant,
MissingLastWithdrawal,
PendingAttestationInElectra,
ExecutionPayloadBidInvalid {
reason: ExecutionPayloadBidInvalid,
},
/// Builder payment index out of bounds (Gloas)
BuilderPaymentIndexOutOfBounds(usize),
}
Expand Down Expand Up @@ -157,6 +160,12 @@ impl From<milhouse::Error> for BlockProcessingError {
}
}

impl From<ExecutionPayloadBidInvalid> for BlockProcessingError {
fn from(reason: ExecutionPayloadBidInvalid) -> Self {
Self::ExecutionPayloadBidInvalid { reason }
}
}

impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
fn from(e: BlockOperationError<HeaderInvalid>) -> BlockProcessingError {
match e {
Expand Down Expand Up @@ -452,6 +461,38 @@ pub enum ExitInvalid {
PendingWithdrawalInQueue(u64),
}

#[derive(Debug, PartialEq, Clone)]
pub enum ExecutionPayloadBidInvalid {
/// The validator set a non-zero amount for a self-build.
SelfBuildNonZeroAmount,
/// The signature is invalid.
BadSignature,
/// The builder is not active.
BuilderNotActive(u64),
/// The builder has insufficient balance to cover the bid
InsufficientBalance {
builder_index: u64,
builder_balance: u64,
bid_value: u64,
},
/// Bid slot doesn't match block slot
SlotMismatch { bid_slot: Slot, block_slot: Slot },
/// The bid's parent block hash doesn't match the state's latest block hash
ParentBlockHashMismatch {
state_block_hash: ExecutionBlockHash,
bid_parent_hash: ExecutionBlockHash,
},
/// The bid's parent block root doesn't match the block's parent root
ParentBlockRootMismatch {
block_parent_root: Hash256,
bid_parent_root: Hash256,
},
/// The bid's prev randao doesn't match the state.
PrevRandaoMismatch { expected: Hash256, bid: Hash256 },
/// The bid contains more than the maximum number of kzg blob commitments.
ExcessBlobCommitments { max: usize, bid: usize },
}

#[derive(Debug, PartialEq, Clone)]
pub enum BlsExecutionChangeInvalid {
/// The specified validator is not in the state's validator registry.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ use tree_hash::TreeHash;
use typenum::Unsigned;
use types::{
AbstractExecPayload, AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError,
ChainSpec, DepositData, Domain, Epoch, EthSpec, Fork, Hash256, InconsistentFork,
BuilderIndex, ChainSpec, DepositData, Domain, Epoch, EthSpec, Fork, Hash256, InconsistentFork,
IndexedAttestation, IndexedAttestationRef, ProposerSlashing, SignedAggregateAndProof,
SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlsToExecutionChange,
SignedContributionAndProof, SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate,
SyncAggregatorSelectionData,
SignedContributionAndProof, SignedExecutionPayloadBid, SignedRoot, SignedVoluntaryExit,
SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData,
consts::gloas::BUILDER_INDEX_SELF_BUILD,
};

pub type Result<T> = std::result::Result<T, Error>;
Expand All @@ -28,6 +29,9 @@ pub enum Error {
/// Attempted to find the public key of a validator that does not exist. You cannot distinguish
/// between an error and an invalid block in this case.
ValidatorUnknown(u64),
/// Attempted to find the public key of a builder that does not exist. You cannot distinguish
/// between an error and an invalid block in this case.
BuilderUnknown(BuilderIndex),
/// Attempted to find the public key of a validator that does not exist. You cannot distinguish
/// between an error and an invalid block in this case.
ValidatorPubkeyUnknown(PublicKeyBytes),
Expand All @@ -53,7 +57,7 @@ impl From<BeaconStateError> for Error {
}
}

/// Helper function to get a public key from a `state`.
/// Helper function to get a validator public key from a `state`.
pub fn get_pubkey_from_state<E>(
state: &BeaconState<E>,
validator_index: usize,
Expand All @@ -71,6 +75,25 @@ where
.map(Cow::Owned)
}

/// Helper function to get a builder public key from a `state`.
pub fn get_builder_pubkey_from_state<E>(
state: &BeaconState<E>,
builder_index: BuilderIndex,
) -> Option<Cow<'_, PublicKey>>
where
E: EthSpec,
{
state
.builders()
.ok()?
.get(builder_index as usize)
.and_then(|b| {
let pk: Option<PublicKey> = b.pubkey.decompress().ok();
pk
})
.map(Cow::Owned)
}

/// A signature set that is valid if a block was signed by the expected block producer.
pub fn block_proposal_signature_set<'a, E, F, Payload: AbstractExecPayload<E>>(
state: &'a BeaconState<E>,
Expand Down Expand Up @@ -332,6 +355,41 @@ where
Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message))
}

pub fn execution_payload_bid_signature_set<'a, E, F>(
state: &'a BeaconState<E>,
get_builder_pubkey: F,
signed_execution_payload_bid: &'a SignedExecutionPayloadBid<E>,
spec: &'a ChainSpec,
) -> Result<Option<SignatureSet<'a>>>
where
E: EthSpec,
F: Fn(BuilderIndex) -> Option<Cow<'a, PublicKey>>,
{
let execution_payload_bid = &signed_execution_payload_bid.message;
let builder_index = execution_payload_bid.builder_index;
if builder_index == BUILDER_INDEX_SELF_BUILD {
// No signatures to verify in case of a self-build, but consensus code MUST check that
// the signature is the point at infinity.
// See `process_execution_payload_bid`.
return Ok(None);
}
let domain = spec.get_domain(
state.current_epoch(),
Domain::BeaconBuilder,
&state.fork(),
state.genesis_validators_root(),
);

let pubkey = get_builder_pubkey(builder_index).ok_or(Error::BuilderUnknown(builder_index))?;
let message = execution_payload_bid.signing_root(domain);

Ok(Some(SignatureSet::single_pubkey(
&signed_execution_payload_bid.signature,
pubkey,
message,
)))
}

/// Returns the signature set for the given `attester_slashing` and corresponding `pubkeys`.
pub fn attester_slashing_signature_sets<'a, E, F>(
state: &'a BeaconState<E>,
Expand Down
11 changes: 10 additions & 1 deletion consensus/types/src/builder/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::test_utils::TestRandom;
use crate::{Address, Epoch, ForkName};
use crate::{Address, ChainSpec, Epoch, ForkName};
use bls::PublicKeyBytes;
use context_deserialize::context_deserialize;
use serde::{Deserialize, Serialize};
Expand All @@ -24,3 +24,12 @@ pub struct Builder {
pub deposit_epoch: Epoch,
pub withdrawable_epoch: Epoch,
}

impl Builder {
/// Check if a builder is active in a state with `finalized_epoch`.
///
/// This implements `is_active_builder` from the spec.
pub fn is_active_at_finalized_epoch(&self, finalized_epoch: Epoch, spec: &ChainSpec) -> bool {
self.deposit_epoch < finalized_epoch && self.withdrawable_epoch == spec.far_future_epoch
}
}
Loading