mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-04 13:24:39 +00:00
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gloas-block-and-bid-production
This commit is contained in:
@@ -5,9 +5,8 @@ authors = ["Paul Hauner <paul@paulhauner.com>", "Michael Sproul <michael@sigmapr
|
||||
edition = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["legacy-arith"]
|
||||
default = []
|
||||
fake_crypto = ["bls/fake_crypto"]
|
||||
legacy-arith = ["types/legacy-arith"]
|
||||
arbitrary-fuzz = [
|
||||
"types/arbitrary-fuzz",
|
||||
"merkle_proof/arbitrary",
|
||||
|
||||
@@ -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,
|
||||
@@ -176,7 +181,7 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
let body = block.body();
|
||||
if state.fork_name_unchecked().gloas_enabled() {
|
||||
withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
|
||||
// TODO(EIP-7732): process execution payload bid
|
||||
process_execution_payload_bid(state, block, verify_signatures, spec)?;
|
||||
} else {
|
||||
if state.fork_name_unchecked().capella_enabled() {
|
||||
withdrawals::capella_electra::process_withdrawals::<E, Payload>(
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ where
|
||||
self.include_exits(block)?;
|
||||
self.include_sync_aggregate(block)?;
|
||||
self.include_bls_to_execution_changes(block)?;
|
||||
self.include_execution_payload_bid(block)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -357,6 +358,27 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Include the signature of the block's execution payload bid.
|
||||
pub fn include_execution_payload_bid<Payload: AbstractExecPayload<E>>(
|
||||
&mut self,
|
||||
block: &'a SignedBeaconBlock<E, Payload>,
|
||||
) -> Result<()> {
|
||||
if let Ok(signed_execution_payload_bid) =
|
||||
block.message().body().signed_execution_payload_bid()
|
||||
{
|
||||
// TODO(gloas): if we implement a global builder pubkey cache we need to inject it here
|
||||
if let Some(signature_set) = execution_payload_bid_signature_set(
|
||||
self.state,
|
||||
|builder_index| get_builder_pubkey_from_state(self.state, builder_index),
|
||||
signed_execution_payload_bid,
|
||||
self.spec,
|
||||
)? {
|
||||
self.sets.push(signature_set);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify all the signatures that have been included in `self`, returning `true` if and only if
|
||||
/// all the signatures are valid.
|
||||
///
|
||||
|
||||
@@ -99,6 +99,9 @@ pub enum BlockProcessingError {
|
||||
IncorrectExpectedWithdrawalsVariant,
|
||||
MissingLastWithdrawal,
|
||||
PendingAttestationInElectra,
|
||||
ExecutionPayloadBidInvalid {
|
||||
reason: ExecutionPayloadBidInvalid,
|
||||
},
|
||||
/// Builder payment index out of bounds (Gloas)
|
||||
BuilderPaymentIndexOutOfBounds(usize),
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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.
|
||||
|
||||
@@ -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>;
|
||||
@@ -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),
|
||||
@@ -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,
|
||||
@@ -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>,
|
||||
@@ -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>,
|
||||
|
||||
@@ -15,9 +15,9 @@ use std::collections::{BTreeSet, HashMap};
|
||||
use tracing::instrument;
|
||||
use typenum::Unsigned;
|
||||
use types::{
|
||||
ActivationQueue, BeaconState, BeaconStateError, ChainSpec, Checkpoint, DepositData, Epoch,
|
||||
EthSpec, ExitCache, ForkName, ParticipationFlags, PendingDeposit, ProgressiveBalancesCache,
|
||||
RelativeEpoch, Validator,
|
||||
ActivationQueue, BeaconState, BeaconStateError, BuilderPendingPayment, ChainSpec, Checkpoint,
|
||||
DepositData, Epoch, EthSpec, ExitCache, ForkName, ParticipationFlags, PendingDeposit,
|
||||
ProgressiveBalancesCache, RelativeEpoch, Validator,
|
||||
consts::altair::{
|
||||
NUM_FLAG_INDICES, PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX,
|
||||
TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR,
|
||||
@@ -33,6 +33,7 @@ pub struct SinglePassConfig {
|
||||
pub pending_consolidations: bool,
|
||||
pub effective_balance_updates: bool,
|
||||
pub proposer_lookahead: bool,
|
||||
pub builder_pending_payments: bool,
|
||||
}
|
||||
|
||||
impl Default for SinglePassConfig {
|
||||
@@ -52,6 +53,7 @@ impl SinglePassConfig {
|
||||
pending_consolidations: true,
|
||||
effective_balance_updates: true,
|
||||
proposer_lookahead: true,
|
||||
builder_pending_payments: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +67,7 @@ impl SinglePassConfig {
|
||||
pending_consolidations: false,
|
||||
effective_balance_updates: false,
|
||||
proposer_lookahead: false,
|
||||
builder_pending_payments: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,6 +458,12 @@ pub fn process_epoch_single_pass<E: EthSpec>(
|
||||
)?;
|
||||
}
|
||||
|
||||
// Process builder pending payments outside the single-pass loop, as they depend on balances for
|
||||
// multiple validators and cannot be computed accurately inside the loop.
|
||||
if fork_name.gloas_enabled() && conf.builder_pending_payments {
|
||||
process_builder_pending_payments(state, state_ctxt, spec)?;
|
||||
}
|
||||
|
||||
// Finally, finish updating effective balance caches. We need this to happen *after* processing
|
||||
// of pending consolidations, which recomputes some effective balances.
|
||||
if conf.effective_balance_updates {
|
||||
@@ -503,6 +512,58 @@ pub fn process_proposer_lookahead<E: EthSpec>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calculate the quorum threshold for builder payments based on total active balance.
|
||||
fn get_builder_payment_quorum_threshold<E: EthSpec>(
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, Error> {
|
||||
let per_slot_balance = state_ctxt
|
||||
.total_active_balance
|
||||
.safe_div(E::slots_per_epoch())?;
|
||||
let quorum = per_slot_balance.safe_mul(spec.builder_payment_threshold_numerator)?;
|
||||
quorum
|
||||
.safe_div(spec.builder_payment_threshold_denominator)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Processes the builder pending payments from the previous epoch.
|
||||
fn process_builder_pending_payments<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let quorum = get_builder_payment_quorum_threshold::<E>(state_ctxt, spec)?;
|
||||
|
||||
// Collect qualifying payments and append to `builder_pending_withdrawals`.
|
||||
// We use this pattern rather than a loop to avoid multiple borrows of the state's fields.
|
||||
let new_pending_builder_withdrawals = state
|
||||
.builder_pending_payments()?
|
||||
.iter()
|
||||
.take(E::SlotsPerEpoch::to_usize())
|
||||
.filter(|payment| payment.weight >= quorum)
|
||||
.map(|payment| payment.withdrawal.clone())
|
||||
.collect::<Vec<_>>();
|
||||
for payment_withdrawal in new_pending_builder_withdrawals {
|
||||
state
|
||||
.builder_pending_withdrawals_mut()?
|
||||
.push(payment_withdrawal)?;
|
||||
}
|
||||
|
||||
// NOTE: this could be a little more memory-efficient with some juggling to reuse parts
|
||||
// of the persistent tree (could convert to list, use pop_front, convert back).
|
||||
let updated_payments = state
|
||||
.builder_pending_payments()?
|
||||
.iter()
|
||||
.skip(E::SlotsPerEpoch::to_usize())
|
||||
.cloned()
|
||||
.chain((0..E::SlotsPerEpoch::to_usize()).map(|_| BuilderPendingPayment::default()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
*state.builder_pending_payments_mut()? = Vector::new(updated_payments)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_single_inactivity_update(
|
||||
inactivity_score: &mut Cow<u64>,
|
||||
validator_info: &ValidatorInfo,
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum Error {
|
||||
EpochProcessingError(EpochProcessingError),
|
||||
ArithError(ArithError),
|
||||
InconsistentStateFork(InconsistentFork),
|
||||
BitfieldError(ssz::BitfieldError),
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
@@ -22,6 +23,12 @@ impl From<ArithError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ssz::BitfieldError> for Error {
|
||||
fn from(e: ssz::BitfieldError) -> Self {
|
||||
Self::BitfieldError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances a state forward by one slot, performing per-epoch processing if required.
|
||||
///
|
||||
/// If the root of the supplied `state` is known, then it can be passed as `state_root`. If
|
||||
@@ -48,6 +55,18 @@ pub fn per_slot_processing<E: EthSpec>(
|
||||
None
|
||||
};
|
||||
|
||||
// Unset the next payload availability
|
||||
if state.fork_name_unchecked().gloas_enabled() {
|
||||
let next_slot_index = state
|
||||
.slot()
|
||||
.as_usize()
|
||||
.safe_add(1)?
|
||||
.safe_rem(E::slots_per_historical_root())?;
|
||||
state
|
||||
.execution_payload_availability_mut()?
|
||||
.set(next_slot_index, false)?;
|
||||
}
|
||||
|
||||
state.slot_mut().safe_add_assign(1)?;
|
||||
|
||||
// Process fork upgrades here. Note that multiple upgrades can potentially run
|
||||
|
||||
@@ -8,9 +8,10 @@ authors = [
|
||||
edition = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["legacy-arith"]
|
||||
# Allow saturating arithmetic on slots and epochs. Enabled by default, but deprecated.
|
||||
legacy-arith = []
|
||||
default = []
|
||||
# Enable +, -, *, /, % operators for Slot and Epoch types.
|
||||
# Operations saturate instead of wrapping.
|
||||
saturating-arith = []
|
||||
sqlite = ["dep:rusqlite"]
|
||||
arbitrary = [
|
||||
"dep:arbitrary",
|
||||
|
||||
@@ -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};
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
test_utils::TestRandom,
|
||||
};
|
||||
|
||||
#[cfg(feature = "legacy-arith")]
|
||||
#[cfg(feature = "saturating-arith")]
|
||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
|
||||
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
|
||||
@@ -117,7 +117,7 @@ macro_rules! impl_safe_arith {
|
||||
}
|
||||
|
||||
// Deprecated: prefer `SafeArith` methods for new code.
|
||||
#[cfg(feature = "legacy-arith")]
|
||||
#[cfg(feature = "saturating-arith")]
|
||||
macro_rules! impl_math_between {
|
||||
($main: ident, $other: ident) => {
|
||||
impl Add<$other> for $main {
|
||||
@@ -321,9 +321,9 @@ macro_rules! impl_common {
|
||||
impl_u64_eq_ord!($type);
|
||||
impl_safe_arith!($type, $type);
|
||||
impl_safe_arith!($type, u64);
|
||||
#[cfg(feature = "legacy-arith")]
|
||||
#[cfg(feature = "saturating-arith")]
|
||||
impl_math_between!($type, $type);
|
||||
#[cfg(feature = "legacy-arith")]
|
||||
#[cfg(feature = "saturating-arith")]
|
||||
impl_math_between!($type, u64);
|
||||
impl_math!($type);
|
||||
impl_display!($type);
|
||||
|
||||
@@ -9,7 +9,7 @@ use fixed_bytes::FixedBytesExtended;
|
||||
use int_to_bytes::{int_to_bytes4, int_to_bytes8};
|
||||
use metastruct::{NumFields, metastruct};
|
||||
use milhouse::{List, Vector};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use safe_arith::{ArithError, SafeArith, SafeArithIter};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use ssz::{Decode, DecodeError, Encode, ssz_encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -218,6 +218,7 @@ pub enum BeaconStateError {
|
||||
envelope_epoch: Epoch,
|
||||
},
|
||||
InvalidIndicesCount,
|
||||
InvalidBuilderPendingPaymentsIndex(usize),
|
||||
InvalidExecutionPayloadAvailabilityIndex(usize),
|
||||
}
|
||||
|
||||
@@ -875,7 +876,7 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
relative_epoch: RelativeEpoch,
|
||||
) -> Result<u64, BeaconStateError> {
|
||||
let cache = self.committee_cache(relative_epoch)?;
|
||||
Ok(cache.epoch_committee_count() as u64)
|
||||
Ok(cache.epoch_committee_count()? as u64)
|
||||
}
|
||||
|
||||
/// Return the cached active validator indices at some epoch.
|
||||
@@ -2149,7 +2150,7 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
) -> Result<Option<AttestationDuty>, BeaconStateError> {
|
||||
let cache = self.committee_cache(relative_epoch)?;
|
||||
|
||||
Ok(cache.get_attestation_duties(validator_index))
|
||||
Ok(cache.get_attestation_duties(validator_index)?)
|
||||
}
|
||||
|
||||
/// Check if the attestation is for the block proposed at the attestation slot.
|
||||
@@ -2749,6 +2750,30 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
Ok(pending_balance)
|
||||
}
|
||||
|
||||
pub fn get_pending_balance_to_withdraw_for_builder(
|
||||
&self,
|
||||
builder_index: BuilderIndex,
|
||||
) -> Result<u64, BeaconStateError> {
|
||||
let pending_withdrawals_total = self
|
||||
.builder_pending_withdrawals()?
|
||||
.iter()
|
||||
.filter_map(|withdrawal| {
|
||||
(withdrawal.builder_index == builder_index).then_some(withdrawal.amount)
|
||||
})
|
||||
.safe_sum()?;
|
||||
let pending_payments_total = self
|
||||
.builder_pending_payments()?
|
||||
.iter()
|
||||
.filter_map(|payment| {
|
||||
(payment.withdrawal.builder_index == builder_index)
|
||||
.then_some(payment.withdrawal.amount)
|
||||
})
|
||||
.safe_sum()?;
|
||||
pending_withdrawals_total
|
||||
.safe_add(pending_payments_total)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
// ******* Electra mutators *******
|
||||
|
||||
pub fn queue_excess_active_balance(
|
||||
@@ -2884,7 +2909,6 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::arithmetic_side_effects)]
|
||||
pub fn rebase_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
|
||||
// Required for macros (which use type-hints internally).
|
||||
|
||||
@@ -3193,7 +3217,6 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::arithmetic_side_effects)]
|
||||
pub fn apply_pending_mutations(&mut self) -> Result<(), BeaconStateError> {
|
||||
match self {
|
||||
Self::Base(inner) => {
|
||||
@@ -3296,7 +3319,6 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
|
||||
pub fn get_beacon_state_leaves(&self) -> Vec<Hash256> {
|
||||
let mut leaves = vec![];
|
||||
#[allow(clippy::arithmetic_side_effects)]
|
||||
match self {
|
||||
BeaconState::Base(state) => {
|
||||
map_beacon_state_base_fields!(state, |_, field| {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#![allow(clippy::arithmetic_side_effects)]
|
||||
|
||||
use std::{num::NonZeroUsize, ops::Range, sync::Arc};
|
||||
|
||||
use educe::Educe;
|
||||
use safe_arith::SafeArith;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssz::{Decode, DecodeError, Encode, four_byte_option_impl};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -79,7 +77,13 @@ impl CommitteeCache {
|
||||
.saturating_sub(spec.min_seed_lookahead)
|
||||
.saturating_sub(1u64);
|
||||
|
||||
if reqd_randao_epoch < state.min_randao_epoch() || epoch > state.current_epoch() + 1 {
|
||||
if reqd_randao_epoch < state.min_randao_epoch()
|
||||
|| epoch
|
||||
> state
|
||||
.current_epoch()
|
||||
.safe_add(1)
|
||||
.map_err(BeaconStateError::ArithError)?
|
||||
{
|
||||
return Err(BeaconStateError::EpochOutOfBounds);
|
||||
}
|
||||
|
||||
@@ -118,7 +122,7 @@ impl CommitteeCache {
|
||||
*shuffling_positions
|
||||
.get_mut(v)
|
||||
.ok_or(BeaconStateError::ShuffleIndexOutOfBounds(v))? =
|
||||
NonZeroUsize::new(i + 1).into();
|
||||
NonZeroUsize::new(i.safe_add(1).map_err(BeaconStateError::ArithError)?).into();
|
||||
}
|
||||
|
||||
Ok(Arc::new(CommitteeCache {
|
||||
@@ -177,8 +181,9 @@ impl CommitteeCache {
|
||||
self.slots_per_epoch as usize,
|
||||
self.committees_per_slot as usize,
|
||||
index as usize,
|
||||
);
|
||||
let committee = self.compute_committee(committee_index)?;
|
||||
)
|
||||
.ok()?;
|
||||
let committee = self.compute_committee(committee_index).ok()??;
|
||||
|
||||
Some(BeaconCommittee {
|
||||
slot,
|
||||
@@ -212,8 +217,9 @@ impl CommitteeCache {
|
||||
.initialized_epoch
|
||||
.ok_or(BeaconStateError::CommitteeCacheUninitialized(None))?;
|
||||
|
||||
let capacity = self.epoch_committee_count()?;
|
||||
initialized_epoch.slot_iter(self.slots_per_epoch).try_fold(
|
||||
Vec::with_capacity(self.epoch_committee_count()),
|
||||
Vec::with_capacity(capacity),
|
||||
|mut vec, slot| {
|
||||
vec.append(&mut self.get_beacon_committees_at_slot(slot)?);
|
||||
Ok(vec)
|
||||
@@ -225,43 +231,53 @@ impl CommitteeCache {
|
||||
///
|
||||
/// Returns `None` if the `validator_index` does not exist, does not have duties or `Self` is
|
||||
/// non-initialized.
|
||||
pub fn get_attestation_duties(&self, validator_index: usize) -> Option<AttestationDuty> {
|
||||
let i = self.shuffled_position(validator_index)?;
|
||||
pub fn get_attestation_duties(
|
||||
&self,
|
||||
validator_index: usize,
|
||||
) -> Result<Option<AttestationDuty>, ArithError> {
|
||||
let Some(i) = self.shuffled_position(validator_index) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
(0..self.epoch_committee_count())
|
||||
.map(|nth_committee| (nth_committee, self.compute_committee_range(nth_committee)))
|
||||
.find(|(_, range)| {
|
||||
if let Some(range) = range {
|
||||
range.start <= i && range.end > i
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.and_then(|(nth_committee, range)| {
|
||||
let (slot, index) = self.convert_to_slot_and_index(nth_committee as u64)?;
|
||||
let range = range?;
|
||||
let committee_position = i - range.start;
|
||||
let committee_len = range.end - range.start;
|
||||
for nth_committee in 0..self.epoch_committee_count()? {
|
||||
let Some(range) = self.compute_committee_range(nth_committee)? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
Some(AttestationDuty {
|
||||
if range.start <= i && range.end > i {
|
||||
let Some((slot, index)) = self.convert_to_slot_and_index(nth_committee as u64)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let committee_position = i.safe_sub(range.start)?;
|
||||
let committee_len = range.end.safe_sub(range.start)?;
|
||||
|
||||
return Ok(Some(AttestationDuty {
|
||||
slot,
|
||||
index,
|
||||
committee_position,
|
||||
committee_len,
|
||||
committees_at_slot: self.committees_per_slot(),
|
||||
})
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Convert an index addressing the list of all epoch committees into a slot and per-slot index.
|
||||
fn convert_to_slot_and_index(
|
||||
&self,
|
||||
global_committee_index: u64,
|
||||
) -> Option<(Slot, CommitteeIndex)> {
|
||||
let epoch_start_slot = self.initialized_epoch?.start_slot(self.slots_per_epoch);
|
||||
let slot_offset = global_committee_index / self.committees_per_slot;
|
||||
let index = global_committee_index % self.committees_per_slot;
|
||||
Some((epoch_start_slot.safe_add(slot_offset).ok()?, index))
|
||||
) -> Result<Option<(Slot, CommitteeIndex)>, ArithError> {
|
||||
let Some(epoch) = self.initialized_epoch else {
|
||||
return Ok(None);
|
||||
};
|
||||
let epoch_start_slot = epoch.start_slot(self.slots_per_epoch);
|
||||
let slot_offset = global_committee_index.safe_div(self.committees_per_slot)?;
|
||||
let index = global_committee_index.safe_rem(self.committees_per_slot)?;
|
||||
Ok(Some((epoch_start_slot.safe_add(slot_offset)?, index)))
|
||||
}
|
||||
|
||||
/// Returns the number of active validators in the initialized epoch.
|
||||
@@ -278,11 +294,8 @@ impl CommitteeCache {
|
||||
/// Always returns `usize::default()` for a non-initialized epoch.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn epoch_committee_count(&self) -> usize {
|
||||
epoch_committee_count(
|
||||
self.committees_per_slot as usize,
|
||||
self.slots_per_epoch as usize,
|
||||
)
|
||||
pub fn epoch_committee_count(&self) -> Result<usize, ArithError> {
|
||||
(self.committees_per_slot as usize).safe_mul(self.slots_per_epoch as usize)
|
||||
}
|
||||
|
||||
/// Returns the number of committees per slot for this cache's epoch.
|
||||
@@ -293,19 +306,23 @@ impl CommitteeCache {
|
||||
/// Returns a slice of `self.shuffling` that represents the `index`'th committee in the epoch.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
fn compute_committee(&self, index: usize) -> Option<&[usize]> {
|
||||
self.shuffling.get(self.compute_committee_range(index)?)
|
||||
fn compute_committee(&self, index: usize) -> Result<Option<&[usize]>, ArithError> {
|
||||
if let Some(range) = self.compute_committee_range(index)? {
|
||||
Ok(self.shuffling.get(range))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a range of `self.shuffling` that represents the `index`'th committee in the epoch.
|
||||
///
|
||||
/// To avoid a divide-by-zero, returns `None` if `self.committee_count` is zero.
|
||||
/// To avoid a divide-by-zero, returns `Ok(None)` if `self.committee_count` is zero.
|
||||
///
|
||||
/// Will also return `None` if the index is out of bounds.
|
||||
/// Will also return `Ok(None)` if the index is out of bounds.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
fn compute_committee_range(&self, index: usize) -> Option<Range<usize>> {
|
||||
compute_committee_range_in_epoch(self.epoch_committee_count(), index, self.shuffling.len())
|
||||
fn compute_committee_range(&self, index: usize) -> Result<Option<Range<usize>>, ArithError> {
|
||||
compute_committee_range_in_epoch(self.epoch_committee_count()?, index, self.shuffling.len())
|
||||
}
|
||||
|
||||
/// Returns the index of some validator in `self.shuffling`.
|
||||
@@ -329,8 +346,10 @@ pub fn compute_committee_index_in_epoch(
|
||||
slots_per_epoch: usize,
|
||||
committees_per_slot: usize,
|
||||
committee_index: usize,
|
||||
) -> usize {
|
||||
(slot.as_usize() % slots_per_epoch) * committees_per_slot + committee_index
|
||||
) -> Result<usize, ArithError> {
|
||||
(slot.as_usize().safe_rem(slots_per_epoch)?)
|
||||
.safe_mul(committees_per_slot)?
|
||||
.safe_add(committee_index)
|
||||
}
|
||||
|
||||
/// Computes the range for slicing the shuffled indices to determine the members of a committee.
|
||||
@@ -341,20 +360,16 @@ pub fn compute_committee_range_in_epoch(
|
||||
epoch_committee_count: usize,
|
||||
index_in_epoch: usize,
|
||||
shuffling_len: usize,
|
||||
) -> Option<Range<usize>> {
|
||||
) -> Result<Option<Range<usize>>, ArithError> {
|
||||
if epoch_committee_count == 0 || index_in_epoch >= epoch_committee_count {
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let start = (shuffling_len * index_in_epoch) / epoch_committee_count;
|
||||
let end = (shuffling_len * (index_in_epoch + 1)) / epoch_committee_count;
|
||||
let start = (shuffling_len.safe_mul(index_in_epoch))?.safe_div(epoch_committee_count)?;
|
||||
let end =
|
||||
(shuffling_len.safe_mul(index_in_epoch.safe_add(1)?))?.safe_div(epoch_committee_count)?;
|
||||
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
/// Returns the total number of committees in an epoch.
|
||||
pub fn epoch_committee_count(committees_per_slot: usize, slots_per_epoch: usize) -> usize {
|
||||
committees_per_slot * slots_per_epoch
|
||||
Ok(Some(start..end))
|
||||
}
|
||||
|
||||
/// Returns a list of all `validators` indices where the validator is active at the given
|
||||
|
||||
@@ -21,7 +21,7 @@ pub use beacon_state::{
|
||||
};
|
||||
pub use committee_cache::{
|
||||
CommitteeCache, compute_committee_index_in_epoch, compute_committee_range_in_epoch,
|
||||
epoch_committee_count, get_active_validator_indices,
|
||||
get_active_validator_indices,
|
||||
};
|
||||
pub use epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey};
|
||||
pub use exit_cache::ExitCache;
|
||||
|
||||
@@ -33,9 +33,9 @@ fn default_values() {
|
||||
assert!(!cache.is_initialized_at(Epoch::new(0)));
|
||||
assert!(&cache.active_validator_indices().is_empty());
|
||||
assert_eq!(cache.get_beacon_committee(Slot::new(0), 0), None);
|
||||
assert_eq!(cache.get_attestation_duties(0), None);
|
||||
assert_eq!(cache.get_attestation_duties(0), Ok(None));
|
||||
assert_eq!(cache.active_validator_count(), 0);
|
||||
assert_eq!(cache.epoch_committee_count(), 0);
|
||||
assert_eq!(cache.epoch_committee_count(), Ok(0));
|
||||
assert!(cache.get_beacon_committees_at_slot(Slot::new(0)).is_err());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user