Use head state for exit verification (#4183)

## Issue Addressed

NA

## Proposed Changes

Similar to #4181 but without the version bump and a more nuanced fix.

Patches the high CPU usage seen after the Capella fork which was caused by processing exits when there are skip slots.

## Additional Info

~~This is an imperfect solution that will cause us to drop some exits at the fork boundary. This is tracked at #4184.~~
This commit is contained in:
Paul Hauner
2023-04-14 01:11:46 +00:00
parent 56dba96319
commit 2b3084f578
8 changed files with 106 additions and 23 deletions

View File

@@ -282,7 +282,8 @@ pub fn process_exits<T: EthSpec>(
// Verify and apply each exit in series. We iterate in series because higher-index exits may
// become invalid due to the application of lower-index ones.
for (i, exit) in voluntary_exits.iter().enumerate() {
verify_exit(state, exit, verify_signatures, spec).map_err(|e| e.into_with_index(i))?;
verify_exit(state, None, exit, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;
initiate_validator_exit(state, exit.message.validator_index as usize, spec)?;
}

View File

@@ -978,8 +978,14 @@ async fn fork_spanning_exit() {
let head = harness.chain.canonical_head.cached_head();
let head_state = &head.snapshot.beacon_state;
assert!(head_state.current_epoch() < spec.altair_fork_epoch.unwrap());
verify_exit(head_state, &signed_exit, VerifySignatures::True, &spec)
.expect("phase0 exit verifies against phase0 state");
verify_exit(
head_state,
None,
&signed_exit,
VerifySignatures::True,
&spec,
)
.expect("phase0 exit verifies against phase0 state");
/*
* Ensure the exit verifies after Altair.
@@ -992,8 +998,14 @@ async fn fork_spanning_exit() {
let head_state = &head.snapshot.beacon_state;
assert!(head_state.current_epoch() >= spec.altair_fork_epoch.unwrap());
assert!(head_state.current_epoch() < spec.bellatrix_fork_epoch.unwrap());
verify_exit(head_state, &signed_exit, VerifySignatures::True, &spec)
.expect("phase0 exit verifies against altair state");
verify_exit(
head_state,
None,
&signed_exit,
VerifySignatures::True,
&spec,
)
.expect("phase0 exit verifies against altair state");
/*
* Ensure the exit no longer verifies after Bellatrix.
@@ -1009,6 +1021,12 @@ async fn fork_spanning_exit() {
let head = harness.chain.canonical_head.cached_head();
let head_state = &head.snapshot.beacon_state;
assert!(head_state.current_epoch() >= spec.bellatrix_fork_epoch.unwrap());
verify_exit(head_state, &signed_exit, VerifySignatures::True, &spec)
.expect_err("phase0 exit does not verify against bellatrix state");
verify_exit(
head_state,
None,
&signed_exit,
VerifySignatures::True,
&spec,
)
.expect_err("phase0 exit does not verify against bellatrix state");
}

View File

@@ -20,10 +20,12 @@ fn error(reason: ExitInvalid) -> BlockOperationError<ExitInvalid> {
/// Spec v0.12.1
pub fn verify_exit<T: EthSpec>(
state: &BeaconState<T>,
current_epoch: Option<Epoch>,
signed_exit: &SignedVoluntaryExit,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
let current_epoch = current_epoch.unwrap_or(state.current_epoch());
let exit = &signed_exit.message;
let validator = state
@@ -33,7 +35,7 @@ pub fn verify_exit<T: EthSpec>(
// Verify the validator is active.
verify!(
validator.is_active_at(state.current_epoch()),
validator.is_active_at(current_epoch),
ExitInvalid::NotActive(exit.validator_index)
);
@@ -45,9 +47,9 @@ pub fn verify_exit<T: EthSpec>(
// Exits must specify an epoch when they become valid; they are not valid before then.
verify!(
state.current_epoch() >= exit.epoch,
current_epoch >= exit.epoch,
ExitInvalid::FutureEpoch {
state: state.current_epoch(),
state: current_epoch,
exit: exit.epoch
}
);
@@ -57,9 +59,9 @@ pub fn verify_exit<T: EthSpec>(
.activation_epoch
.safe_add(spec.shard_committee_period)?;
verify!(
state.current_epoch() >= earliest_exit_epoch,
current_epoch >= earliest_exit_epoch,
ExitInvalid::TooYoungToExit {
current_epoch: state.current_epoch(),
current_epoch,
earliest_exit_epoch,
}
);