Skip to content

Commit

Permalink
Merge pull request #252 from PatStiles/feat/process_withdrawals
Browse files Browse the repository at this point in the history
feat(capella): implement process_withdrawals
  • Loading branch information
ralexstokes authored Sep 21, 2023
2 parents afbf742 + 94f427e commit 3c14c21
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 16 deletions.
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

0 comments on commit 3c14c21

Please sign in to comment.