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:
- 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).
- 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(); |
Description
Presently we do a state advance for every block produced with withdrawals, here:
lighthouse/beacon_node/beacon_chain/src/beacon_chain.rs
Lines 4703 to 4718 in 5108872
This happens during
fcUwhen 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:
execution_layer. Re-wire the function to attempt to load them from theexecution_layeron subsequent calls (e.g. for the fork choice runs at 11.5s and 0s). The withdrawals are already stored in theproposersmap 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 theBeaconState.Block processing:
lighthouse/consensus/state_processing/src/per_block_processing.rs
Lines 527 to 528 in 5108872