Core execution logic that applies a block's state transitions to any ConsensusState. This method: - Calls check_payload on the engine client (validates and optimistically executes the block on the EVM) - Applies consensus-layer state transitions (deposits, withdrawals, validators) - Updates the forkchoice head - Creates checkpoints at epoch boundaries This does NOT handle epoch transitions (acti
(
engine_client: &mut C,
context: &ContextCell<R>,
block: &Block,
state: &mut ConsensusState,
consts: &ProtocolConsts,
protocol_version_digest: Digest,
)
| 1312 | Ok(stake_changed) => stake_changed, |
| 1313 | Err(e) => { |
| 1314 | warn!("skipping invalid protocol parameter changes at epoch boundary: {e}"); |
| 1315 | false |
| 1316 | } |
| 1317 | }; |
| 1318 | |
| 1319 | // Build the committee for the next epoch. |
| 1320 | self.validator_exit = self.update_validator_committee(stake_changed); |
| 1321 | |
| 1322 | // Reschedule any overflow withdrawals that exceeded the per-epoch |
| 1323 | // total withdrawal cap to the next epoch. |
| 1324 | let current_epoch = self.canonical_state.get_epoch(); |
| 1325 | if self |
| 1326 | .canonical_state |
| 1327 | .get_withdrawal_count_for_epoch(current_epoch) |
| 1328 | > 0 |
| 1329 | { |
| 1330 | let overflow_count = self |
| 1331 | .canonical_state |
| 1332 | .get_withdrawal_count_for_epoch(current_epoch); |
| 1333 | info!( |
| 1334 | current_epoch, |
| 1335 | overflow_count, "rescheduling overflow withdrawals to next epoch" |
| 1336 | ); |
| 1337 | self.canonical_state |
| 1338 | .reschedule_withdrawal_epoch(current_epoch, current_epoch + 1); |
| 1339 | } |
| 1340 | |
| 1341 | #[cfg(feature = "prom")] |
| 1342 | let db_operations_start = Instant::now(); |
| 1343 | // This pending checkpoint should always exist, because it was created at the previous height. |
| 1344 | // The only case where the pending checkpoint doesn't exist here is if the node checkpointed. |
| 1345 | // The checkpoint is created at the penultimate block of the epoch, and finalized at the last |
| 1346 | // block. So if a node checkpoints, it will start at the height of the penultimate block. |
| 1347 | if let Some(checkpoint) = &self.canonical_state.take_pending_checkpoint() { |
| 1348 | debug!( |
| 1349 | epoch = self.canonical_state.get_epoch(), |
| 1350 | checkpoint_digest = ?checkpoint.digest, |
| 1351 | "storing finalized checkpoint to database" |
| 1352 | ); |
| 1353 | #[cfg(feature = "prom")] |
| 1354 | let checkpoint_start = Instant::now(); |
| 1355 | self.db |
| 1356 | .store_finalized_checkpoint( |
| 1357 | self.canonical_state.get_epoch(), |
| 1358 | checkpoint, |
| 1359 | block.clone(), |
| 1360 | ) |
| 1361 | .await?; |
| 1362 | #[cfg(feature = "prom")] |
| 1363 | { |
| 1364 | let checkpoint_duration = checkpoint_start.elapsed().as_micros() as f64; |
| 1365 | histogram!("finalizer_db_checkpoint_write_micros").record(checkpoint_duration); |
| 1366 | } |
| 1367 | } |
| 1368 | |
| 1369 | // Increment epoch |
| 1370 | let next_epoch = self.canonical_state.get_epoch() + 1; |
| 1371 | self.canonical_state.set_epoch(next_epoch); |
no test coverage detected