Skip to content

Cache withdrawals prior to block production #3896

@michaelsproul

Description

@michaelsproul

Description

Presently we do a state advance for every block produced with withdrawals, here:

let withdrawals = match self.spec.fork_name_at_slot::<T::EthSpec>(prepare_slot) {
ForkName::Base | ForkName::Altair | ForkName::Merge => None,
ForkName::Capella | ForkName::Eip4844 => {
// We must use the advanced state because balances can change at epoch boundaries
// and balances affect withdrawals.
// FIXME(mark)
// Might implement caching here in the future..
let prepare_state = self
.state_at_slot(prepare_slot, StateSkipConfig::WithoutStateRoots)
.map_err(|e| {
error!(self.log, "State advance for withdrawals failed"; "error" => ?e);
e
})?;
Some(get_expected_withdrawals(&prepare_state, &self.spec))
}
}

This happens during fcU when constructing the payload attributes, so it's kind of already off the hot path. However, we recalculate the withdrawals for every call.

To fix this I think there are a few options:

  1. Try to use the advanced state from the snapshot cache. Downsides: state advance happens at 9s which is usually after the first payload preparation call at 8s, and the state advance only gives a single slot of advancement (doesn't work for multiple skips).
  2. Compute the withdrawals the same way we do currently the first time the function is called (at 8s) and cache them in the execution_layer. Re-wire the function to attempt to load them from the execution_layer on subsequent calls (e.g. for the fork choice runs at 11.5s and 0s). The withdrawals are already stored in the proposers map on the execution layer.

I think I prefer the 2nd solution at the moment.

We may also be able to benefit from a cache for block processing, although with the sweep length capped at 16K the iteration shouldn't take very long (by my estimate <2ms, even for tree-states). For block processing we'd probably want the cache as a (Hash256, Vec<Withdrawal>) on the BeaconState.

Block processing:

let expected_withdrawals = get_expected_withdrawals(state, spec)?;
let expected_root = expected_withdrawals.tree_hash_root();

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions