Updated for queueless withdrawals spec

This commit is contained in:
Mark Mackey
2022-11-09 18:09:07 -06:00
parent bc0af72c74
commit ab13f95db5
20 changed files with 209 additions and 224 deletions

View File

@@ -4,7 +4,6 @@ mod get_attesting_indices;
mod get_indexed_attestation;
mod initiate_validator_exit;
mod slash_validator;
mod withdraw_balance;
pub mod altair;
pub mod base;
@@ -15,8 +14,6 @@ pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_fro
pub use get_indexed_attestation::get_indexed_attestation;
pub use initiate_validator_exit::initiate_validator_exit;
pub use slash_validator::slash_validator;
#[cfg(feature = "withdrawals")]
pub use withdraw_balance::withdraw_balance;
use safe_arith::SafeArith;
use types::{BeaconState, BeaconStateError, EthSpec};

View File

@@ -1,29 +0,0 @@
use crate::common::decrease_balance;
use safe_arith::SafeArith;
use types::{BeaconStateError as Error, *};
#[cfg(feature = "withdrawals")]
pub fn withdraw_balance<T: EthSpec>(
state: &mut BeaconState<T>,
validator_index: usize,
amount: u64,
) -> Result<(), Error> {
decrease_balance(state, validator_index as usize, amount)?;
let withdrawal_address = Address::from_slice(
&state
.get_validator(validator_index)?
.withdrawal_credentials
.as_bytes()[12..],
);
let withdrawal = Withdrawal {
index: *state.next_withdrawal_index()?,
validator_index: validator_index as u64,
address: withdrawal_address,
amount,
};
state.next_withdrawal_index_mut()?.safe_add_assign(1)?;
state.withdrawal_queue_mut()?.push(withdrawal)?;
Ok(())
}

View File

@@ -19,6 +19,8 @@ pub use process_operations::process_operations;
pub use verify_attestation::{
verify_attestation_for_block_inclusion, verify_attestation_for_state,
};
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub use verify_bls_to_execution_change::verify_bls_to_execution_change;
pub use verify_deposit::{
get_existing_validator_index, verify_deposit_merkle_proof, verify_deposit_signature,
};
@@ -34,6 +36,8 @@ pub mod signature_sets;
pub mod tests;
mod verify_attestation;
mod verify_attester_slashing;
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
mod verify_bls_to_execution_change;
mod verify_deposit;
mod verify_exit;
mod verify_proposer_slashing;

View File

@@ -49,6 +49,10 @@ pub enum BlockProcessingError {
index: usize,
reason: ExitInvalid,
},
BlsExecutionChangeInvalid {
index: usize,
reason: BlsExecutionChangeInvalid,
},
SyncAggregateInvalid {
reason: SyncAggregateInvalid,
},
@@ -180,7 +184,8 @@ impl_into_block_processing_error_with_index!(
IndexedAttestationInvalid,
AttestationInvalid,
DepositInvalid,
ExitInvalid
ExitInvalid,
BlsExecutionChangeInvalid
);
pub type HeaderValidationError = BlockOperationError<HeaderInvalid>;
@@ -190,6 +195,7 @@ pub type AttestationValidationError = BlockOperationError<AttestationInvalid>;
pub type SyncCommitteeMessageValidationError = BlockOperationError<SyncAggregateInvalid>;
pub type DepositValidationError = BlockOperationError<DepositInvalid>;
pub type ExitValidationError = BlockOperationError<ExitInvalid>;
pub type BlsExecutionChangeValidationError = BlockOperationError<BlsExecutionChangeInvalid>;
#[derive(Debug, PartialEq, Clone)]
pub enum BlockOperationError<T> {
@@ -405,6 +411,18 @@ pub enum ExitInvalid {
SignatureSetError(SignatureSetError),
}
#[derive(Debug, PartialEq, Clone)]
pub enum BlsExecutionChangeInvalid {
/// The specified validator is not in the state's validator registry.
ValidatorUnknown(u64),
/// Validator does not have BLS Withdrawal credentials before this change
NonBlsWithdrawalCredentials,
/// Provided BLS pubkey does not match withdrawal credentials
WithdrawalCredentialsMismatch,
/// The signature is invalid
BadSignature,
}
#[derive(Debug, PartialEq, Clone)]
pub enum SyncAggregateInvalid {
/// One or more of the aggregate public keys is invalid.

View File

@@ -33,6 +33,9 @@ pub fn process_operations<'a, T: EthSpec, Payload: AbstractExecPayload<T>>(
process_attestations(state, block_body, verify_signatures, ctxt, spec)?;
process_deposits(state, block_body.deposits(), spec)?;
process_exits(state, block_body.voluntary_exits(), verify_signatures, spec)?;
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
process_bls_to_execution_changes(state, block_body, verify_signatures, spec)?;
Ok(())
}
@@ -279,6 +282,46 @@ pub fn process_exits<T: EthSpec>(
Ok(())
}
/// Validates each `bls_to_execution_change` and updates the state
///
/// Returns `Ok(())` if the validation and state updates completed successfully. Otherwise returs
/// an `Err` describing the invalid object or cause of failure.
///
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub fn process_bls_to_execution_changes<'a, T: EthSpec, Payload: AbstractExecPayload<T>>(
state: &mut BeaconState<T>,
block_body: BeaconBlockBodyRef<'a, T, Payload>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
match block_body {
BeaconBlockBodyRef::Base(_)
| BeaconBlockBodyRef::Altair(_)
| BeaconBlockBodyRef::Merge(_) => Ok(()),
BeaconBlockBodyRef::Capella(_) | BeaconBlockBodyRef::Eip4844(_) => {
for (i, signed_address_change) in block_body.bls_to_execution_changes()?.enumerate() {
verify_bls_to_execution_change(
state,
&signed_address_change,
verify_signatures,
spec,
)
.map_err(|e| e.into_with_index(i))?;
state
.get_validator_mut(signed_address_change.message.validator_index)?
.change_withdrawal_credentials(
signed_address_change.message.to_execution_address,
spec,
);
}
Ok(())
}
}
}
/// Validates each `Deposit` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns

View File

@@ -11,8 +11,8 @@ use types::{
BeaconStateError, ChainSpec, DepositData, Domain, Epoch, EthSpec, Fork, Hash256,
InconsistentFork, IndexedAttestation, ProposerSlashing, PublicKey, PublicKeyBytes, Signature,
SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader,
SignedContributionAndProof, SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate,
SyncAggregatorSelectionData, Unsigned,
SignedBlsToExecutionChange, SignedContributionAndProof, SignedRoot, SignedVoluntaryExit,
SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned,
};
pub type Result<T> = std::result::Result<T, Error>;
@@ -156,6 +156,33 @@ where
))
}
pub fn bls_execution_change_signature_set<'a, T: EthSpec>(
state: &'a BeaconState<T>,
signed_address_change: &'a SignedBlsToExecutionChange,
spec: &'a ChainSpec,
) -> Result<SignatureSet<'a>> {
let domain = spec.get_domain(
state.current_epoch(),
Domain::BlsToExecutionChange,
&state.fork(),
state.genesis_validators_root(),
);
let message = signed_address_change.message.signing_root(domain);
let signing_key = Cow::Owned(
signed_address_change
.message
.from_bls_pubkey
.decompress()
.map_err(|_| Error::PublicKeyDecompressionFailed)?,
);
Ok(SignatureSet::single_pubkey(
&signed_address_change.signature,
signing_key,
message,
))
}
/// A signature set that is valid if the block proposers randao reveal signature is correct.
pub fn randao_signature_set<'a, T, F, Payload: AbstractExecPayload<T>>(
state: &'a BeaconState<T>,

View File

@@ -11,7 +11,6 @@ pub use weigh_justification_and_finalization::weigh_justification_and_finalizati
pub mod altair;
pub mod base;
pub mod capella;
pub mod effective_balance_updates;
pub mod epoch_processing_summary;
pub mod errors;
@@ -38,8 +37,10 @@ pub fn process_epoch<T: EthSpec>(
match state {
BeaconState::Base(_) => base::process_epoch(state, spec),
BeaconState::Altair(_) | BeaconState::Merge(_) => altair::process_epoch(state, spec),
BeaconState::Capella(_) | BeaconState::Eip4844(_) => capella::process_epoch(state, spec),
BeaconState::Altair(_)
| BeaconState::Merge(_)
| BeaconState::Capella(_)
| BeaconState::Eip4844(_) => altair::process_epoch(state, spec),
}
}

View File

@@ -1,87 +0,0 @@
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
use crate::per_epoch_processing::{
altair,
effective_balance_updates::process_effective_balance_updates,
historical_roots_update::process_historical_roots_update,
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
};
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub use full_withdrawals::process_full_withdrawals;
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub use partial_withdrawals::process_partial_withdrawals;
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub mod full_withdrawals;
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub mod partial_withdrawals;
pub fn process_epoch<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<EpochProcessingSummary<T>, Error> {
// Ensure the committee caches are built.
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
state.build_committee_cache(RelativeEpoch::Next, spec)?;
// Pre-compute participating indices and total balances.
let participation_cache = altair::ParticipationCache::new(state, spec)?;
let sync_committee = state.current_sync_committee()?.clone();
// Justification and finalization.
let justification_and_finalization_state =
altair::process_justification_and_finalization(state, &participation_cache)?;
justification_and_finalization_state.apply_changes_to_state(state);
altair::process_inactivity_updates(state, &participation_cache, spec)?;
// Rewards and Penalties.
altair::process_rewards_and_penalties(state, &participation_cache, spec)?;
// Registry Updates.
process_registry_updates(state, spec)?;
// Slashings.
process_slashings(
state,
participation_cache.current_epoch_total_active_balance(),
spec,
)?;
// Reset eth1 data votes.
process_eth1_data_reset(state)?;
// Update effective balances with hysteresis (lag).
process_effective_balance_updates(state, spec)?;
// Reset slashings
process_slashings_reset(state)?;
// Set randao mix
process_randao_mixes_reset(state)?;
// Set historical root accumulator
process_historical_roots_update(state)?;
// Rotate current/previous epoch participation
altair::process_participation_flag_updates(state)?;
altair::process_sync_committee_updates(state, spec)?;
// Withdrawals
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
process_full_withdrawals(state, spec)?;
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
process_partial_withdrawals(state, spec)?;
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches(spec)?;
// FIXME: do we need a Capella variant for this?
Ok(EpochProcessingSummary::Altair {
participation_cache,
sync_committee,
})
}

View File

@@ -1,25 +0,0 @@
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
use crate::common::withdraw_balance;
use crate::EpochProcessingError;
use types::{beacon_state::BeaconState, eth_spec::EthSpec, ChainSpec};
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub fn process_full_withdrawals<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), EpochProcessingError> {
let current_epoch = state.current_epoch();
// FIXME: is this the most efficient way to do this?
for validator_index in 0..state.validators().len() {
// TODO: is this the correct way to handle validators not existing?
if let (Some(validator), Some(balance)) = (
state.validators().get(validator_index),
state.balances().get(validator_index),
) {
if validator.is_fully_withdrawable_at(*balance, current_epoch, spec) {
withdraw_balance(state, validator_index, *balance)?;
}
}
}
Ok(())
}

View File

@@ -1,41 +0,0 @@
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
use crate::common::withdraw_balance;
use crate::EpochProcessingError;
use safe_arith::SafeArith;
use types::{beacon_state::BeaconState, eth_spec::EthSpec, ChainSpec};
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub fn process_partial_withdrawals<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), EpochProcessingError> {
let mut partial_withdrawals_count = 0;
let mut validator_index = *state.next_partial_withdrawal_validator_index()? as usize;
let n_validators = state.validators().len();
// FIXME: is this the most efficient way to do this?
for _ in 0..n_validators {
// TODO: is this the correct way to handle validators not existing?
if let (Some(validator), Some(balance)) = (
state.validators().get(validator_index),
state.balances().get(validator_index),
) {
if validator.is_partially_withdrawable_validator(*balance, spec) {
withdraw_balance(
state,
validator_index,
*balance - spec.max_effective_balance,
)?;
partial_withdrawals_count.safe_add_assign(1)?;
validator_index = validator_index.safe_add(1)? % n_validators;
if partial_withdrawals_count == T::max_partial_withdrawals_per_epoch() {
break;
}
}
}
}
*state.next_partial_withdrawal_validator_index_mut()? = validator_index as u64;
Ok(())
}

View File

@@ -11,7 +11,7 @@ pub fn upgrade_to_eip4844<E: EthSpec>(
// FIXME(sean) This is a hack to let us participate in testnets where capella doesn't exist.
// if we are disabling withdrawals, assume we should fork off of bellatrix.
let previous_fork_version = if cfg!(feature ="withdrawals") {
let previous_fork_version = if cfg!(feature = "withdrawals") {
pre.fork.current_version
} else {
spec.bellatrix_fork_version