Refine op pool pruning (#1805)

## Issue Addressed

Closes #1769
Closes #1708

## Proposed Changes

Tweaks the op pool pruning so that the attestation pool is pruned against the wall-clock epoch instead of the finalized state's epoch. This should reduce the unbounded growth that we've seen during periods without finality.

Also fixes up the voluntary exit pruning as raised in #1708.
This commit is contained in:
Michael Sproul 2020-10-22 04:47:29 +00:00
parent a3704b971e
commit 7f73dccebc
2 changed files with 27 additions and 19 deletions

View File

@ -1959,6 +1959,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|| is_reorg || is_reorg
{ {
self.persist_head_and_fork_choice()?; self.persist_head_and_fork_choice()?;
self.op_pool.prune_attestations(self.epoch()?);
} }
let update_head_timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES); let update_head_timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES);
@ -2097,8 +2098,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.get_state(&new_finalized_state_root, None)? .get_state(&new_finalized_state_root, None)?
.ok_or_else(|| Error::MissingBeaconState(new_finalized_state_root))?; .ok_or_else(|| Error::MissingBeaconState(new_finalized_state_root))?;
self.op_pool self.op_pool.prune_all(
.prune_all(&finalized_state, self.head_info()?.fork); &finalized_state,
self.epoch()?,
self.head_info()?.fork,
&self.spec,
);
self.store_migrator.process_finalization( self.store_migrator.process_finalization(
new_finalized_state_root.into(), new_finalized_state_root.into(),

View File

@ -21,8 +21,8 @@ use std::marker::PhantomData;
use std::ptr; use std::ptr;
use types::{ use types::{
typenum::Unsigned, Attestation, AttesterSlashing, BeaconState, BeaconStateError, ChainSpec, typenum::Unsigned, Attestation, AttesterSlashing, BeaconState, BeaconStateError, ChainSpec,
EthSpec, Fork, ForkVersion, Hash256, ProposerSlashing, RelativeEpoch, SignedVoluntaryExit, Epoch, EthSpec, Fork, ForkVersion, Hash256, ProposerSlashing, RelativeEpoch,
Validator, SignedVoluntaryExit, Validator,
}; };
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct OperationPool<T: EthSpec + Default> { pub struct OperationPool<T: EthSpec + Default> {
@ -156,17 +156,14 @@ impl<T: EthSpec> OperationPool<T> {
} }
/// Remove attestations which are too old to be included in a block. /// Remove attestations which are too old to be included in a block.
pub fn prune_attestations(&self, finalized_state: &BeaconState<T>) { pub fn prune_attestations(&self, current_epoch: Epoch) {
// We know we can include an attestation if: // Prune attestations that are from before the previous epoch.
// state.slot <= attestation_slot + SLOTS_PER_EPOCH
// We approximate this check using the attestation's epoch, to avoid computing
// the slot or relying on the committee cache of the finalized state.
self.attestations.write().retain(|_, attestations| { self.attestations.write().retain(|_, attestations| {
// All the attestations in this bucket have the same data, so we only need to // All the attestations in this bucket have the same data, so we only need to
// check the first one. // check the first one.
attestations.first().map_or(false, |att| { attestations
finalized_state.current_epoch() <= att.data.target.epoch + 1 .first()
}) .map_or(false, |att| current_epoch <= att.data.target.epoch + 1)
}); });
} }
@ -299,20 +296,26 @@ impl<T: EthSpec> OperationPool<T> {
} }
/// Prune if validator has already exited at the last finalized state. /// Prune if validator has already exited at the last finalized state.
pub fn prune_voluntary_exits(&self, finalized_state: &BeaconState<T>) { pub fn prune_voluntary_exits(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
prune_validator_hash_map( prune_validator_hash_map(
&mut self.voluntary_exits.write(), &mut self.voluntary_exits.write(),
|validator| validator.is_exited_at(finalized_state.current_epoch()), |validator| validator.exit_epoch != spec.far_future_epoch,
finalized_state, finalized_state,
); );
} }
/// Prune all types of transactions given the latest finalized state and head fork. /// Prune all types of transactions given the latest finalized state and head fork.
pub fn prune_all(&self, finalized_state: &BeaconState<T>, head_fork: Fork) { pub fn prune_all(
self.prune_attestations(finalized_state); &self,
finalized_state: &BeaconState<T>,
current_epoch: Epoch,
head_fork: Fork,
spec: &ChainSpec,
) {
self.prune_attestations(current_epoch);
self.prune_proposer_slashings(finalized_state); self.prune_proposer_slashings(finalized_state);
self.prune_attester_slashings(finalized_state, head_fork); self.prune_attester_slashings(finalized_state, head_fork);
self.prune_voluntary_exits(finalized_state); self.prune_voluntary_exits(finalized_state, spec);
} }
/// Total number of voluntary exits in the pool. /// Total number of voluntary exits in the pool.
@ -612,13 +615,13 @@ mod release_tests {
); );
// Prune attestations shouldn't do anything at this point. // Prune attestations shouldn't do anything at this point.
op_pool.prune_attestations(state); op_pool.prune_attestations(state.current_epoch());
assert_eq!(op_pool.num_attestations(), committees.len()); assert_eq!(op_pool.num_attestations(), committees.len());
// But once we advance to more than an epoch after the attestation, it should prune it // But once we advance to more than an epoch after the attestation, it should prune it
// out of existence. // out of existence.
state.slot += 2 * MainnetEthSpec::slots_per_epoch(); state.slot += 2 * MainnetEthSpec::slots_per_epoch();
op_pool.prune_attestations(state); op_pool.prune_attestations(state.current_epoch());
assert_eq!(op_pool.num_attestations(), 0); assert_eq!(op_pool.num_attestations(), 0);
} }