Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions beacon_node/beacon_chain/src/canonical_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,11 +654,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
})
})
.and_then(|mut snapshot| {
// Regardless of where we got the state from, attempt to build the committee
// caches.
// Regardless of where we got the state from, attempt to build all the
// caches except the tree hash cache.
snapshot
.beacon_state
.build_all_committee_caches(&self.spec)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be a little heavy handed to build the exit cache here as well, but I don't feel too strongly about that. IIRC we don't always need the exit cache to process a block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figure it's quasi-equivalent to building it on-demand when it is eventually required. If we process a block and it becomes head, chances are its descendants will remain canonical and the exit cache will be required eventually, at which point we've paid once to build it (when the first ancestor was processed). The only case where I think it'll hurt is if there are lots of re-orgs that miss the snapshot cache and load a fresh state without any caches. We hope that these are rare. These re-orgs and these sorts of caching issues are also mitigated by tree-states, where all the states share common CoW caches rooted at the finalized state.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My (potentially shoddy) understanding is that the exit cache is only required when we're processing exits, which is relatively infrequently. However, given that:

  • The cache we're building is on the head state.
  • The head state usually maintains it's caches (i.e., they aren't dropped and rebuilt often)
  • Trying to build an already-built exit cache is cheap.

I'm happy with this. I'll slap another approval!

.build_all_caches(&self.spec)
.map_err(Into::into)
.map(|()| snapshot)
})?;
Expand Down
21 changes: 19 additions & 2 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,17 +668,34 @@ pub fn serve<T: BeaconChainTypes>(
"Invalid validator ID".to_string(),
))
}))
.and(log_filter.clone())
.and(warp::path::end())
.and_then(
|state_id: StateId, chain: Arc<BeaconChain<T>>, validator_id: ValidatorId| {
|state_id: StateId, chain: Arc<BeaconChain<T>>, validator_id: ValidatorId, log| {
blocking_json_task(move || {
let (data, execution_optimistic) = state_id
.map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let index_opt = match &validator_id {
ValidatorId::PublicKey(pubkey) => {
state.validators().iter().position(|v| v.pubkey == *pubkey)
// Fast path: use the pubkey cache which is probably
// initialised at the head.
match state.get_validator_index_read_only(pubkey) {
Ok(result) => result,
Err(e) => {
// Slow path, fall back to iteration.
debug!(
log,
"Validator look-up cache miss";
"reason" => ?e,
);
state
.validators()
.iter()
.position(|v| v.pubkey == *pubkey)
}
}
}
ValidatorId::Index(index) => Some(*index as usize),
};
Expand Down
25 changes: 2 additions & 23 deletions beacon_node/http_api/src/state_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,33 +155,12 @@ impl StateId {
Ok((state, execution_optimistic))
}

/*
/// Map a function across the `BeaconState` identified by `self`.
///
/// The optimistic status of the requested state is also provided to the `func` closure.
///
/// This function will avoid instantiating/copying a new state when `self` points to the head
/// of the chain.
#[allow(dead_code)]
pub fn map_state<T: BeaconChainTypes, F, U>(
&self,
chain: &BeaconChain<T>,
func: F,
) -> Result<U, warp::Rejection>
where
F: Fn(&BeaconState<T::EthSpec>) -> Result<U, warp::Rejection>,
{
match &self.0 {
CoreStateId::Head => chain
.with_head(|snapshot| Ok(func(&snapshot.beacon_state)))
.map_err(warp_utils::reject::beacon_chain_error)?,
_ => func(&self.state(chain)?),
}
}
*/

/// Functions the same as `map_state` but additionally computes the value of
/// `execution_optimistic` of the state identified by `self`.
///
/// This is to avoid re-instantiating `state` unnecessarily.
pub fn map_state_and_execution_optimistic<T: BeaconChainTypes, F, U>(
&self,
chain: &BeaconChain<T>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ pub fn verify_deposit_signature(deposit_data: &DepositData, spec: &ChainSpec) ->
/// Returns a `Some(validator index)` if a pubkey already exists in the `validators`,
/// otherwise returns `None`.
///
/// ## Errors
///
/// Errors if the state's `pubkey_cache` is not current.
/// Builds the pubkey cache if it is not already built.
pub fn get_existing_validator_index<T: EthSpec>(
state: &mut BeaconState<T>,
pub_key: &PublicKeyBytes,
Expand Down
15 changes: 15 additions & 0 deletions consensus/types/src/beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,21 @@ impl<T: EthSpec> BeaconState<T> {
Ok(self.pubkey_cache().get(pubkey))
}

/// Immutable variant of `get_validator_index` which errors if the cache is not up to date.
pub fn get_validator_index_read_only(
&self,
pubkey: &PublicKeyBytes,
) -> Result<Option<usize>, Error> {
let pubkey_cache = self.pubkey_cache();
if pubkey_cache.len() != self.validators().len() {
return Err(Error::PubkeyCacheIncomplete {
cache_len: pubkey_cache.len(),
registry_len: self.validators().len(),
});
}
Ok(pubkey_cache.get(pubkey))
}

/// The epoch corresponding to `self.slot()`.
pub fn current_epoch(&self) -> Epoch {
self.slot().epoch(T::slots_per_epoch())
Expand Down