Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(capella): implement process_withdrawals #252

Merged
merged 11 commits into from
Sep 21, 2023
115 changes: 107 additions & 8 deletions ethereum-consensus/src/capella/block_processing.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use crate::{
capella::{
compute_timestamp_at_slot, get_current_epoch, get_randao_mix, process_attestation,
compute_timestamp_at_slot, decrease_balance, get_current_epoch, get_randao_mix,
is_fully_withdrawable_validator, is_partially_withdrawable_validator, process_attestation,
process_attester_slashing, process_block_header, process_deposit, process_eth1_data,
process_proposer_slashing, process_randao, process_sync_aggregate, process_voluntary_exit,
BeaconBlock, BeaconBlockBody, BeaconState, ExecutionEngine, ExecutionPayload,
ExecutionPayloadHeader, NewPayloadRequest, SignedBlsToExecutionChange,
BeaconBlock, BeaconBlockBody, BeaconState, ExecutionAddress, ExecutionEngine,
ExecutionPayload, ExecutionPayloadHeader, NewPayloadRequest, SignedBlsToExecutionChange,
Withdrawal,
},
ssz::prelude::*,
state_transition::{
invalid_operation_error, Context, InvalidDeposit, InvalidExecutionPayload,
InvalidOperation, Result,
InvalidOperation, InvalidWithdrawals, Result,
},
};

Expand Down Expand Up @@ -240,7 +242,7 @@ pub fn process_withdrawals<
const MAX_TRANSACTIONS_PER_PAYLOAD: usize,
const MAX_WITHDRAWALS_PER_PAYLOAD: usize,
>(
_state: &mut BeaconState<
state: &mut BeaconState<
SLOTS_PER_HISTORICAL_ROOT,
HISTORICAL_ROOTS_LIMIT,
ETH1_DATA_VOTES_BOUND,
Expand All @@ -252,16 +254,113 @@ pub fn process_withdrawals<
BYTES_PER_LOGS_BLOOM,
MAX_EXTRA_DATA_BYTES,
>,
_execution_payload: &ExecutionPayload<
execution_payload: &ExecutionPayload<
BYTES_PER_LOGS_BLOOM,
MAX_EXTRA_DATA_BYTES,
MAX_BYTES_PER_TRANSACTION,
MAX_TRANSACTIONS_PER_PAYLOAD,
MAX_WITHDRAWALS_PER_PAYLOAD,
>,
_context: &Context,
context: &Context,
) -> Result<()> {
unimplemented!()
let expected_withdrawals = get_expected_withdrawals(state, context);

if execution_payload.withdrawals.as_ref() != expected_withdrawals {
return Err(invalid_operation_error(InvalidOperation::Withdrawal(
InvalidWithdrawals::IncorrectWithdrawals {
provided: execution_payload.withdrawals.to_vec(),
expected: expected_withdrawals,
},
)))
}

for withdrawal in &expected_withdrawals {
decrease_balance(state, withdrawal.validator_index, withdrawal.amount);
}

// Update the next withdrawal index if this block contained withdrawals
if let Some(latest_withdrawal) = expected_withdrawals.last() {
state.next_withdrawal_index = latest_withdrawal.index + 1;
}

// Update the next validator index to start the next withdrawal sweep
if expected_withdrawals.len() == context.max_withdrawals_per_payload {
// Next sweep starts after the latest withdrawal's validator index
let latest_withdrawal = expected_withdrawals.last().expect("empty withdrawals");
let next_validator_index = (latest_withdrawal.validator_index + 1) % state.validators.len();
state.next_withdrawal_validator_index = next_validator_index;
} else {
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
let next_index =
state.next_withdrawal_validator_index + context.max_validators_per_withdrawals_sweep;
state.next_withdrawal_validator_index = next_index % state.validators.len();
}
Ok(())
}

pub fn get_expected_withdrawals<
const SLOTS_PER_HISTORICAL_ROOT: usize,
const HISTORICAL_ROOTS_LIMIT: usize,
const ETH1_DATA_VOTES_BOUND: usize,
const VALIDATOR_REGISTRY_LIMIT: usize,
const EPOCHS_PER_HISTORICAL_VECTOR: usize,
const EPOCHS_PER_SLASHINGS_VECTOR: usize,
const MAX_VALIDATORS_PER_COMMITTEE: usize,
const SYNC_COMMITTEE_SIZE: usize,
const BYTES_PER_LOGS_BLOOM: usize,
const MAX_EXTRA_DATA_BYTES: usize,
>(
state: &BeaconState<
SLOTS_PER_HISTORICAL_ROOT,
HISTORICAL_ROOTS_LIMIT,
ETH1_DATA_VOTES_BOUND,
VALIDATOR_REGISTRY_LIMIT,
EPOCHS_PER_HISTORICAL_VECTOR,
EPOCHS_PER_SLASHINGS_VECTOR,
MAX_VALIDATORS_PER_COMMITTEE,
SYNC_COMMITTEE_SIZE,
BYTES_PER_LOGS_BLOOM,
MAX_EXTRA_DATA_BYTES,
>,
context: &Context,
) -> Vec<Withdrawal> {
let epoch = get_current_epoch(state, context);
let mut withdrawal_index = state.next_withdrawal_index;
let mut validator_index = state.next_withdrawal_validator_index;
let mut withdrawals = vec![];
let bound = state.validators.len().min(context.max_validators_per_withdrawals_sweep);
for _ in 0..bound {
let validator = &state.validators[validator_index];
let balance = state.balances[validator_index];
if is_fully_withdrawable_validator(validator, balance, epoch) {
let address =
ExecutionAddress::try_from(&validator.withdrawal_credentials.as_slice()[12..])
.expect("providing the correct amount of input to type");
withdrawals.push(Withdrawal {
index: withdrawal_index,
validator_index,
address,
amount: balance,
});
withdrawal_index += 1;
} else if is_partially_withdrawable_validator(validator, balance, context) {
let address =
ExecutionAddress::try_from(&validator.withdrawal_credentials.as_slice()[12..])
.expect("providing the correct amount of input to type");
withdrawals.push(Withdrawal {
index: withdrawal_index,
validator_index,
address,
amount: balance - context.max_effective_balance,
});
withdrawal_index += 1;
}
if withdrawals.len() == context.max_withdrawals_per_payload {
break
}
validator_index = (validator_index + 1) % state.validators.len();
}
withdrawals
}

pub fn process_block<
Expand Down
83 changes: 75 additions & 8 deletions ethereum-consensus/src/state_transition/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
altair, bellatrix,
altair, bellatrix, capella,
clock::{self, Clock, SystemTimeProvider},
configs::{self, Config},
deneb,
Expand Down Expand Up @@ -73,6 +73,11 @@ pub struct Context {
pub bytes_per_logs_bloom: usize,
pub max_extra_data_bytes: usize,

// capella preset
pub max_bls_to_execution_changes: usize,
pub max_withdrawals_per_payload: usize,
pub max_validators_per_withdrawals_sweep: usize,

// deneb preset
pub field_elements_per_blob: usize,
pub max_blob_commitments_per_block: usize,
Expand Down Expand Up @@ -129,15 +134,31 @@ impl Context {
let phase0_preset = &phase0::mainnet::PRESET;
let altair_preset = &altair::mainnet::PRESET;
let bellatrix_preset = &bellatrix::mainnet::PRESET;
let capella_preset = &capella::mainnet::PRESET;
let deneb_preset = &deneb::mainnet::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, &config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
&config,
)
}
"minimal" => {
let phase0_preset = &phase0::minimal::PRESET;
let altair_preset = &altair::minimal::PRESET;
let bellatrix_preset = &bellatrix::minimal::PRESET;
let capella_preset = &capella::minimal::PRESET;
let deneb_preset = &deneb::minimal::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, &config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
&config,
)
}
other => return Err(Error::UnknownPreset(other.to_string())),
};
Expand All @@ -148,6 +169,7 @@ impl Context {
phase0_preset: &phase0::Preset,
altair_preset: &altair::Preset,
bellatrix_preset: &bellatrix::Preset,
capella_preset: &capella::Preset,
deneb_preset: &deneb::Preset,
config: &Config,
) -> Self {
Expand Down Expand Up @@ -206,6 +228,11 @@ impl Context {
max_transactions_per_payload: bellatrix_preset.max_transactions_per_payload,
bytes_per_logs_bloom: bellatrix_preset.bytes_per_logs_bloom,
max_extra_data_bytes: bellatrix_preset.max_extra_data_bytes,
// capella
max_bls_to_execution_changes: capella_preset.max_bls_to_execution_changes,
max_withdrawals_per_payload: capella_preset.max_withdrawals_per_payload,
max_validators_per_withdrawals_sweep: capella_preset
.max_validators_per_withdrawals_sweep,
// deneb
field_elements_per_blob: deneb_preset.field_elements_per_blob,
max_blob_commitments_per_block: deneb_preset.max_blob_commitments_per_block,
Expand Down Expand Up @@ -250,44 +277,84 @@ impl Context {
let phase0_preset = &phase0::mainnet::PRESET;
let altair_preset = &altair::mainnet::PRESET;
let bellatrix_preset = &bellatrix::mainnet::PRESET;
let capella_preset = &capella::mainnet::PRESET;
let deneb_preset = &deneb::mainnet::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
config,
)
}

pub fn for_minimal() -> Self {
let config = &configs::minimal::config();
let phase0_preset = &phase0::minimal::PRESET;
let altair_preset = &altair::minimal::PRESET;
let bellatrix_preset = &bellatrix::minimal::PRESET;
let capella_preset = &capella::minimal::PRESET;
let deneb_preset = &deneb::minimal::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
config,
)
}

pub fn for_goerli() -> Self {
let config = &configs::goerli::config();
let phase0_preset = &phase0::mainnet::PRESET;
let altair_preset = &altair::mainnet::PRESET;
let bellatrix_preset = &bellatrix::mainnet::PRESET;
let capella_preset = &capella::mainnet::PRESET;
let deneb_preset = &deneb::mainnet::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
config,
)
}

pub fn for_sepolia() -> Self {
let config = &configs::sepolia::config();
let phase0_preset = &phase0::mainnet::PRESET;
let altair_preset = &altair::mainnet::PRESET;
let bellatrix_preset = &bellatrix::mainnet::PRESET;
let capella_preset = &capella::mainnet::PRESET;
let deneb_preset = &deneb::mainnet::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
config,
)
}

pub fn for_holesky() -> Self {
let config = &configs::holesky::config();
let phase0_preset = &phase0::mainnet::PRESET;
let altair_preset = &altair::mainnet::PRESET;
let bellatrix_preset = &bellatrix::mainnet::PRESET;
let capella_preset = &capella::mainnet::PRESET;
let deneb_preset = &deneb::mainnet::PRESET;
Self::from(phase0_preset, altair_preset, bellatrix_preset, deneb_preset, config)
Self::from(
phase0_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
config,
)
}

pub fn fork_for(&self, slot: Slot) -> Forks {
Expand Down
9 changes: 9 additions & 0 deletions ethereum-consensus/src/state_transition/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
capella::Withdrawal,
crypto::Error as CryptoError,
phase0::{AttestationData, BeaconBlockHeader, Checkpoint},
primitives::{BlsSignature, Bytes32, Epoch, Hash32, Root, Slot, ValidatorIndex},
Expand Down Expand Up @@ -84,6 +85,8 @@ pub enum InvalidOperation {
SyncAggregate(#[from] InvalidSyncAggregate),
#[error("invalid execution payload: {0}")]
ExecutionPayload(#[from] InvalidExecutionPayload),
#[error("invalid withdrawals: {0}")]
Withdrawal(#[from] InvalidWithdrawals),
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -180,6 +183,12 @@ pub enum InvalidVoluntaryExit {
InvalidSignature(BlsSignature),
}

#[derive(Debug, Error)]
pub enum InvalidWithdrawals {
#[error("expected withdrawals {expected:#?} do not match provided withdrawals {provided:#?}")]
IncorrectWithdrawals { provided: Vec<Withdrawal>, expected: Vec<Withdrawal> },
}

#[derive(Debug, Error)]
pub enum InvalidSyncAggregate {
#[error("invalid sync committee aggregate signature {signature} signing over previous slot block root {root}")]
Expand Down