Prioritise important parts of block processing (#3696)

## Issue Addressed

Closes https://github.com/sigp/lighthouse/issues/2327

## Proposed Changes

This is an extension of some ideas I implemented while working on `tree-states`:

- Cache the indexed attestations from blocks in the `ConsensusContext`. Previously we were re-computing them 3-4 times over.
- Clean up `import_block` by splitting each part into `import_block_XXX`.
- Move some stuff off hot paths, specifically:
  - Relocate non-essential tasks that were running between receiving the payload verification status and priming the early attester cache. These tasks are moved after the cache priming:
    - Attestation observation
    - Validator monitor updates
    - Slasher updates
    - Updating the shuffling cache
  - Fork choice attestation observation now happens at the end of block verification in parallel with payload verification (this seems to save 5-10ms).
  - Payload verification now happens _before_ advancing the pre-state and writing it to disk! States were previously being written eagerly and adding ~20-30ms in front of verifying the execution payload. State catchup also sometimes takes ~500ms if we get a cache miss and need to rebuild the tree hash cache.

The remaining task that's taking substantial time (~20ms) is importing the block to fork choice. I _think_ this is because of pull-tips, and we should be able to optimise it out with a clever total active balance cache in the state (which would be computed in parallel with payload verification). I've decided to leave that for future work though. For now it can be observed via the new `beacon_block_processing_post_exec_pre_attestable_seconds` metric.


Co-authored-by: Michael Sproul <micsproul@gmail.com>
This commit is contained in:
Michael Sproul
2022-11-30 05:22:58 +00:00
parent b4f4c0d253
commit 22115049ee
14 changed files with 784 additions and 497 deletions

View File

@@ -1,14 +1,13 @@
#![allow(clippy::integer_arithmetic)]
use super::signature_sets::{Error as SignatureSetError, *};
use crate::common::get_indexed_attestation;
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
use crate::{ConsensusContext, ContextError};
use bls::{verify_signature_sets, PublicKey, PublicKeyBytes, SignatureSet};
use rayon::prelude::*;
use std::borrow::Cow;
use types::{
BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, Hash256, IndexedAttestation,
SignedBeaconBlock,
BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, Hash256, SignedBeaconBlock,
};
pub type Result<T> = std::result::Result<T, Error>;
@@ -28,6 +27,8 @@ pub enum Error {
IncorrectBlockProposer { block: u64, local_shuffling: u64 },
/// Failed to load a signature set. The block may be invalid or we failed to process it.
SignatureSetError(SignatureSetError),
/// Error related to the consensus context, likely the proposer index or block root calc.
ContextError(ContextError),
}
impl From<BeaconStateError> for Error {
@@ -36,6 +37,12 @@ impl From<BeaconStateError> for Error {
}
}
impl From<ContextError> for Error {
fn from(e: ContextError) -> Error {
Error::ContextError(e)
}
}
impl From<SignatureSetError> for Error {
fn from(e: SignatureSetError) -> Error {
match e {
@@ -122,12 +129,11 @@ where
get_pubkey: F,
decompressor: D,
block: &'a SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
verified_proposer_index: Option<u64>,
ctxt: &mut ConsensusContext<T>,
spec: &'a ChainSpec,
) -> Result<()> {
let mut verifier = Self::new(state, get_pubkey, decompressor, spec);
verifier.include_all_signatures(block, block_root, verified_proposer_index)?;
verifier.include_all_signatures(block, ctxt)?;
verifier.verify()
}
@@ -135,11 +141,14 @@ where
pub fn include_all_signatures<Payload: ExecPayload<T>>(
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
verified_proposer_index: Option<u64>,
ctxt: &mut ConsensusContext<T>,
) -> Result<()> {
let block_root = Some(ctxt.get_current_block_root(block)?);
let verified_proposer_index =
Some(ctxt.get_proposer_index_from_epoch_state(self.state, self.spec)?);
self.include_block_proposal(block, block_root, verified_proposer_index)?;
self.include_all_signatures_except_proposal(block, verified_proposer_index)?;
self.include_all_signatures_except_proposal(block, ctxt)?;
Ok(())
}
@@ -149,12 +158,14 @@ where
pub fn include_all_signatures_except_proposal<Payload: ExecPayload<T>>(
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
verified_proposer_index: Option<u64>,
ctxt: &mut ConsensusContext<T>,
) -> Result<()> {
let verified_proposer_index =
Some(ctxt.get_proposer_index_from_epoch_state(self.state, self.spec)?);
self.include_randao_reveal(block, verified_proposer_index)?;
self.include_proposer_slashings(block)?;
self.include_attester_slashings(block)?;
self.include_attestations(block)?;
self.include_attestations(block, ctxt)?;
// Deposits are not included because they can legally have invalid signatures.
self.include_exits(block)?;
self.include_sync_aggregate(block)?;
@@ -260,7 +271,8 @@ where
pub fn include_attestations<Payload: ExecPayload<T>>(
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
) -> Result<Vec<IndexedAttestation<T>>> {
ctxt: &mut ConsensusContext<T>,
) -> Result<()> {
self.sets
.sets
.reserve(block.message().body().attestations().len());
@@ -270,28 +282,18 @@ where
.body()
.attestations()
.iter()
.try_fold(
Vec::with_capacity(block.message().body().attestations().len()),
|mut vec, attestation| {
let committee = self
.state
.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation =
get_indexed_attestation(committee.committee, attestation)?;
.try_for_each(|attestation| {
let indexed_attestation = ctxt.get_indexed_attestation(self.state, attestation)?;
self.sets.push(indexed_attestation_signature_set(
self.state,
self.get_pubkey.clone(),
&attestation.signature,
&indexed_attestation,
self.spec,
)?);
vec.push(indexed_attestation);
Ok(vec)
},
)
self.sets.push(indexed_attestation_signature_set(
self.state,
self.get_pubkey.clone(),
&attestation.signature,
indexed_attestation,
self.spec,
)?);
Ok(())
})
.map_err(Error::into)
}

View File

@@ -57,8 +57,14 @@ pub mod base {
// Verify and apply each attestation.
for (i, attestation) in attestations.iter().enumerate() {
verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;
verify_attestation_for_block_inclusion(
state,
attestation,
ctxt,
verify_signatures,
spec,
)
.map_err(|e| e.into_with_index(i))?;
let pending_attestation = PendingAttestation {
aggregation_bits: attestation.aggregation_bits.clone(),
@@ -94,19 +100,11 @@ pub mod altair {
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let proposer_index = ctxt.get_proposer_index(state, spec)?;
attestations
.iter()
.enumerate()
.try_for_each(|(i, attestation)| {
process_attestation(
state,
attestation,
i,
proposer_index,
verify_signatures,
spec,
)
process_attestation(state, attestation, i, ctxt, verify_signatures, spec)
})
}
@@ -114,16 +112,24 @@ pub mod altair {
state: &mut BeaconState<T>,
attestation: &Attestation<T>,
att_index: usize,
proposer_index: u64,
ctxt: &mut ConsensusContext<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
let indexed_attestation =
verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec)
.map_err(|e| e.into_with_index(att_index))?;
let proposer_index = ctxt.get_proposer_index(state, spec)?;
let attesting_indices = &verify_attestation_for_block_inclusion(
state,
attestation,
ctxt,
verify_signatures,
spec,
)
.map_err(|e| e.into_with_index(att_index))?
.attesting_indices;
// Matching roots, participation flag indices
let data = &attestation.data;
@@ -135,7 +141,7 @@ pub mod altair {
let total_active_balance = state.get_total_active_balance()?;
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
let mut proposer_reward_numerator = 0;
for index in &indexed_attestation.attesting_indices {
for index in attesting_indices {
let index = *index as usize;
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {

View File

@@ -1,7 +1,7 @@
use super::errors::{AttestationInvalid as Invalid, BlockOperationError};
use super::VerifySignatures;
use crate::common::get_indexed_attestation;
use crate::per_block_processing::is_valid_indexed_attestation;
use crate::ConsensusContext;
use safe_arith::SafeArith;
use types::*;
@@ -15,12 +15,13 @@ fn error(reason: Invalid) -> BlockOperationError<Invalid> {
/// to `state`. Otherwise, returns a descriptive `Err`.
///
/// Optionally verifies the aggregate signature, depending on `verify_signatures`.
pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
pub fn verify_attestation_for_block_inclusion<'ctxt, T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation<T>,
ctxt: &'ctxt mut ConsensusContext<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<IndexedAttestation<T>> {
) -> Result<&'ctxt IndexedAttestation<T>> {
let data = &attestation.data;
verify!(
@@ -39,7 +40,7 @@ pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
}
);
verify_attestation_for_state(state, attestation, verify_signatures, spec)
verify_attestation_for_state(state, attestation, ctxt, verify_signatures, spec)
}
/// Returns `Ok(())` if `attestation` is a valid attestation to the chain that precedes the given
@@ -49,12 +50,13 @@ pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
/// prior blocks in `state`.
///
/// Spec v0.12.1
pub fn verify_attestation_for_state<T: EthSpec>(
pub fn verify_attestation_for_state<'ctxt, T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation<T>,
ctxt: &'ctxt mut ConsensusContext<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<IndexedAttestation<T>> {
) -> Result<&'ctxt IndexedAttestation<T>> {
let data = &attestation.data;
verify!(
@@ -66,9 +68,8 @@ pub fn verify_attestation_for_state<T: EthSpec>(
verify_casper_ffg_vote(attestation, state)?;
// Check signature and bitfields
let committee = state.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation = get_indexed_attestation(committee.committee, attestation)?;
is_valid_indexed_attestation(state, &indexed_attestation, verify_signatures, spec)?;
let indexed_attestation = ctxt.get_indexed_attestation(state, attestation)?;
is_valid_indexed_attestation(state, indexed_attestation, verify_signatures, spec)?;
Ok(indexed_attestation)
}